commit 074a25db88f068b2d9311fe7102f31f4e57aeb25 Author: Ondřej Hruška Date: Sat May 24 20:58:25 2014 +0200 Initial commit diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..d36d6b7 --- /dev/null +++ b/.classpath @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..509450b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/bin/ +/target/ +*.log +.attach_pid* +*~ diff --git a/.project b/.project new file mode 100644 index 0000000..c9e8916 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Tortuga-game + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..8000cd6 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/lib/OpenAL32.dll b/lib/OpenAL32.dll new file mode 100644 index 0000000..6dd2600 Binary files /dev/null and b/lib/OpenAL32.dll differ diff --git a/lib/OpenAL64.dll b/lib/OpenAL64.dll new file mode 100644 index 0000000..00c98c0 Binary files /dev/null and b/lib/OpenAL64.dll differ diff --git a/lib/jdom-2.0.3-sources.zip b/lib/jdom-2.0.3-sources.zip new file mode 100644 index 0000000..b6b7ef2 Binary files /dev/null and b/lib/jdom-2.0.3-sources.zip differ diff --git a/lib/jdom-2.0.3.jar b/lib/jdom-2.0.3.jar new file mode 100644 index 0000000..821fa3e Binary files /dev/null and b/lib/jdom-2.0.3.jar differ diff --git a/lib/jinput-dx8.dll b/lib/jinput-dx8.dll new file mode 100644 index 0000000..6d27ad5 Binary files /dev/null and b/lib/jinput-dx8.dll differ diff --git a/lib/jinput-dx8_64.dll b/lib/jinput-dx8_64.dll new file mode 100644 index 0000000..6730589 Binary files /dev/null and b/lib/jinput-dx8_64.dll differ diff --git a/lib/jinput-raw.dll b/lib/jinput-raw.dll new file mode 100644 index 0000000..ce1d162 Binary files /dev/null and b/lib/jinput-raw.dll differ diff --git a/lib/jinput-raw_64.dll b/lib/jinput-raw_64.dll new file mode 100644 index 0000000..3d2b3ad Binary files /dev/null and b/lib/jinput-raw_64.dll differ diff --git a/lib/jinput.jar b/lib/jinput.jar new file mode 100644 index 0000000..7c2b6b0 Binary files /dev/null and b/lib/jinput.jar differ diff --git a/lib/jogg-0.0.7.jar b/lib/jogg-0.0.7.jar new file mode 100644 index 0000000..ecb0260 Binary files /dev/null and b/lib/jogg-0.0.7.jar differ diff --git a/lib/jorbis-0.0.15.jar b/lib/jorbis-0.0.15.jar new file mode 100644 index 0000000..4cf51f9 Binary files /dev/null and b/lib/jorbis-0.0.15.jar differ diff --git a/lib/libjinput-linux.so b/lib/libjinput-linux.so new file mode 100644 index 0000000..3cdc439 Binary files /dev/null and b/lib/libjinput-linux.so differ diff --git a/lib/libjinput-linux64.so b/lib/libjinput-linux64.so new file mode 100644 index 0000000..de1ee5f Binary files /dev/null and b/lib/libjinput-linux64.so differ diff --git a/lib/libjinput-osx.jnilib b/lib/libjinput-osx.jnilib new file mode 100644 index 0000000..59a3eab Binary files /dev/null and b/lib/libjinput-osx.jnilib differ diff --git a/lib/liblwjgl.jnilib b/lib/liblwjgl.jnilib new file mode 100644 index 0000000..cbb5b4c Binary files /dev/null and b/lib/liblwjgl.jnilib differ diff --git a/lib/liblwjgl.so b/lib/liblwjgl.so new file mode 100644 index 0000000..5a02874 Binary files /dev/null and b/lib/liblwjgl.so differ diff --git a/lib/liblwjgl64.so b/lib/liblwjgl64.so new file mode 100644 index 0000000..4572589 Binary files /dev/null and b/lib/liblwjgl64.so differ diff --git a/lib/libopenal.so b/lib/libopenal.so new file mode 100644 index 0000000..7742faf Binary files /dev/null and b/lib/libopenal.so differ diff --git a/lib/libopenal64.so b/lib/libopenal64.so new file mode 100644 index 0000000..d1e45e5 Binary files /dev/null and b/lib/libopenal64.so differ diff --git a/lib/lwjgl-source-2.8.4.zip b/lib/lwjgl-source-2.8.4.zip new file mode 100644 index 0000000..f48fd31 Binary files /dev/null and b/lib/lwjgl-source-2.8.4.zip differ diff --git a/lib/lwjgl.dll b/lib/lwjgl.dll new file mode 100644 index 0000000..6819404 Binary files /dev/null and b/lib/lwjgl.dll differ diff --git a/lib/lwjgl.jar b/lib/lwjgl.jar new file mode 100644 index 0000000..a0fb56d Binary files /dev/null and b/lib/lwjgl.jar differ diff --git a/lib/lwjgl64.dll b/lib/lwjgl64.dll new file mode 100644 index 0000000..e66ab2a Binary files /dev/null and b/lib/lwjgl64.dll differ diff --git a/lib/lwjgl_util.jar b/lib/lwjgl_util.jar new file mode 100644 index 0000000..9973b24 Binary files /dev/null and b/lib/lwjgl_util.jar differ diff --git a/lib/slick-util-src.zip b/lib/slick-util-src.zip new file mode 100644 index 0000000..7ca6790 Binary files /dev/null and b/lib/slick-util-src.zip differ diff --git a/lib/slick-util.jar b/lib/slick-util.jar new file mode 100644 index 0000000..86a64a7 Binary files /dev/null and b/lib/slick-util.jar differ diff --git a/res/fonts/4feb.ttf b/res/fonts/4feb.ttf new file mode 100644 index 0000000..668bdaf Binary files /dev/null and b/res/fonts/4feb.ttf differ diff --git a/res/images/gui/buttons_menu.png b/res/images/gui/buttons_menu.png new file mode 100644 index 0000000..615e93b Binary files /dev/null and b/res/images/gui/buttons_menu.png differ diff --git a/res/images/gui/buttons_small.png b/res/images/gui/buttons_small.png new file mode 100644 index 0000000..8674c42 Binary files /dev/null and b/res/images/gui/buttons_small.png differ diff --git a/res/images/gui/icons.png b/res/images/gui/icons.png new file mode 100644 index 0000000..ae9490b Binary files /dev/null and b/res/images/gui/icons.png differ diff --git a/res/images/gui/logo.png b/res/images/gui/logo.png new file mode 100644 index 0000000..75df7f5 Binary files /dev/null and b/res/images/gui/logo.png differ diff --git a/res/images/gui/widgets.png b/res/images/gui/widgets.png new file mode 100644 index 0000000..667896e Binary files /dev/null and b/res/images/gui/widgets.png differ diff --git a/res/images/particles/circle.png b/res/images/particles/circle.png new file mode 100644 index 0000000..674eb93 Binary files /dev/null and b/res/images/particles/circle.png differ diff --git a/res/images/particles/leaves.png b/res/images/particles/leaves.png new file mode 100644 index 0000000..bad1f65 Binary files /dev/null and b/res/images/particles/leaves.png differ diff --git a/res/images/particles/rain.png b/res/images/particles/rain.png new file mode 100644 index 0000000..4647c45 Binary files /dev/null and b/res/images/particles/rain.png differ diff --git a/res/images/particles/snow.png b/res/images/particles/snow.png new file mode 100644 index 0000000..ca5ca18 Binary files /dev/null and b/res/images/particles/snow.png differ diff --git a/res/images/patterns/rivet_belts.png b/res/images/patterns/rivet_belts.png new file mode 100644 index 0000000..32b3c7a Binary files /dev/null and b/res/images/patterns/rivet_belts.png differ diff --git a/res/images/patterns/steel.png b/res/images/patterns/steel.png new file mode 100644 index 0000000..cb33eb2 Binary files /dev/null and b/res/images/patterns/steel.png differ diff --git a/res/images/patterns/steel_big.png b/res/images/patterns/steel_big.png new file mode 100644 index 0000000..9296a6d Binary files /dev/null and b/res/images/patterns/steel_big.png differ diff --git a/res/images/patterns/steel_big_light.png b/res/images/patterns/steel_big_light.png new file mode 100644 index 0000000..82e6594 Binary files /dev/null and b/res/images/patterns/steel_big_light.png differ diff --git a/res/images/patterns/steel_big_scratched.png b/res/images/patterns/steel_big_scratched.png new file mode 100644 index 0000000..8bcef8f Binary files /dev/null and b/res/images/patterns/steel_big_scratched.png differ diff --git a/res/images/patterns/steel_brushed.png b/res/images/patterns/steel_brushed.png new file mode 100644 index 0000000..82d8108 Binary files /dev/null and b/res/images/patterns/steel_brushed.png differ diff --git a/res/images/patterns/steel_light.png b/res/images/patterns/steel_light.png new file mode 100644 index 0000000..08b5fa4 Binary files /dev/null and b/res/images/patterns/steel_light.png differ diff --git a/res/images/patterns/water.png b/res/images/patterns/water.png new file mode 100644 index 0000000..9c84274 Binary files /dev/null and b/res/images/patterns/water.png differ diff --git a/res/images/program/program.png b/res/images/program/program.png new file mode 100644 index 0000000..1430c5b Binary files /dev/null and b/res/images/program/program.png differ diff --git a/res/images/sprites/HD-blue.png b/res/images/sprites/HD-blue.png new file mode 100644 index 0000000..5a9649f Binary files /dev/null and b/res/images/sprites/HD-blue.png differ diff --git a/res/images/sprites/HD-brown-dk.png b/res/images/sprites/HD-brown-dk.png new file mode 100644 index 0000000..285b9fa Binary files /dev/null and b/res/images/sprites/HD-brown-dk.png differ diff --git a/res/images/sprites/HD-brown-lt.png b/res/images/sprites/HD-brown-lt.png new file mode 100644 index 0000000..b528b78 Binary files /dev/null and b/res/images/sprites/HD-brown-lt.png differ diff --git a/res/images/sprites/HD-brown-ornamental.png b/res/images/sprites/HD-brown-ornamental.png new file mode 100644 index 0000000..e8b1c9e Binary files /dev/null and b/res/images/sprites/HD-brown-ornamental.png differ diff --git a/res/images/sprites/HD-green.png b/res/images/sprites/HD-green.png new file mode 100644 index 0000000..64742ad Binary files /dev/null and b/res/images/sprites/HD-green.png differ diff --git a/res/images/sprites/HD-purple.png b/res/images/sprites/HD-purple.png new file mode 100644 index 0000000..dcd9aa3 Binary files /dev/null and b/res/images/sprites/HD-purple.png differ diff --git a/res/images/sprites/HD-red.png b/res/images/sprites/HD-red.png new file mode 100644 index 0000000..031b4fd Binary files /dev/null and b/res/images/sprites/HD-red.png differ diff --git a/res/images/sprites/HD-shadow.png b/res/images/sprites/HD-shadow.png new file mode 100644 index 0000000..fbaf673 Binary files /dev/null and b/res/images/sprites/HD-shadow.png differ diff --git a/res/images/sprites/HD-yellow.png b/res/images/sprites/HD-yellow.png new file mode 100644 index 0000000..d743f4e Binary files /dev/null and b/res/images/sprites/HD-yellow.png differ diff --git a/res/images/sprites/food-shadow.png b/res/images/sprites/food-shadow.png new file mode 100644 index 0000000..2763e9a Binary files /dev/null and b/res/images/sprites/food-shadow.png differ diff --git a/res/images/sprites/food.png b/res/images/sprites/food.png new file mode 100644 index 0000000..d234503 Binary files /dev/null and b/res/images/sprites/food.png differ diff --git a/res/images/tiles/shading.png b/res/images/tiles/shading.png new file mode 100644 index 0000000..2e4a3da Binary files /dev/null and b/res/images/tiles/shading.png differ diff --git a/res/images/tiles/tiles.png b/res/images/tiles/tiles.png new file mode 100644 index 0000000..2a6ec76 Binary files /dev/null and b/res/images/tiles/tiles.png differ diff --git a/res/levels/manifest.xml b/res/levels/manifest.xml new file mode 100644 index 0000000..3f041d8 --- /dev/null +++ b/res/levels/manifest.xml @@ -0,0 +1,9 @@ + + + + + + 01.xml + + + \ No newline at end of file diff --git a/res/sounds/effects/bell.ogg b/res/sounds/effects/bell.ogg new file mode 100644 index 0000000..92ac2bb Binary files /dev/null and b/res/sounds/effects/bell.ogg differ diff --git a/res/sounds/effects/block_drop.ogg b/res/sounds/effects/block_drop.ogg new file mode 100644 index 0000000..1bbc14e Binary files /dev/null and b/res/sounds/effects/block_drop.ogg differ diff --git a/res/sounds/effects/block_pick.ogg b/res/sounds/effects/block_pick.ogg new file mode 100644 index 0000000..18b4d90 Binary files /dev/null and b/res/sounds/effects/block_pick.ogg differ diff --git a/res/sounds/effects/block_put.ogg b/res/sounds/effects/block_put.ogg new file mode 100644 index 0000000..3ff2f21 Binary files /dev/null and b/res/sounds/effects/block_put.ogg differ diff --git a/res/sounds/effects/break_fruit.ogg b/res/sounds/effects/break_fruit.ogg new file mode 100644 index 0000000..2487470 Binary files /dev/null and b/res/sounds/effects/break_fruit.ogg differ diff --git a/res/sounds/effects/click_button1.ogg b/res/sounds/effects/click_button1.ogg new file mode 100644 index 0000000..1121ae4 Binary files /dev/null and b/res/sounds/effects/click_button1.ogg differ diff --git a/res/sounds/effects/click_button2.ogg b/res/sounds/effects/click_button2.ogg new file mode 100644 index 0000000..712f439 Binary files /dev/null and b/res/sounds/effects/click_button2.ogg differ diff --git a/res/sounds/effects/click_open.ogg b/res/sounds/effects/click_open.ogg new file mode 100644 index 0000000..0e98060 Binary files /dev/null and b/res/sounds/effects/click_open.ogg differ diff --git a/res/sounds/effects/click_rattle.ogg b/res/sounds/effects/click_rattle.ogg new file mode 100644 index 0000000..a61263c Binary files /dev/null and b/res/sounds/effects/click_rattle.ogg differ diff --git a/res/sounds/effects/click_switch.ogg b/res/sounds/effects/click_switch.ogg new file mode 100644 index 0000000..8f207f0 Binary files /dev/null and b/res/sounds/effects/click_switch.ogg differ diff --git a/res/sounds/effects/click_tray.ogg b/res/sounds/effects/click_tray.ogg new file mode 100644 index 0000000..6021e30 Binary files /dev/null and b/res/sounds/effects/click_tray.ogg differ diff --git a/res/sounds/effects/click_typewriter1.ogg b/res/sounds/effects/click_typewriter1.ogg new file mode 100644 index 0000000..9384fa7 Binary files /dev/null and b/res/sounds/effects/click_typewriter1.ogg differ diff --git a/res/sounds/effects/click_typewriter2.ogg b/res/sounds/effects/click_typewriter2.ogg new file mode 100644 index 0000000..d4cc54c Binary files /dev/null and b/res/sounds/effects/click_typewriter2.ogg differ diff --git a/res/sounds/effects/eat.ogg b/res/sounds/effects/eat.ogg new file mode 100644 index 0000000..5b7803e Binary files /dev/null and b/res/sounds/effects/eat.ogg differ diff --git a/res/sounds/effects/error.ogg b/res/sounds/effects/error.ogg new file mode 100644 index 0000000..fd363e2 Binary files /dev/null and b/res/sounds/effects/error.ogg differ diff --git a/res/sounds/effects/gear1.ogg b/res/sounds/effects/gear1.ogg new file mode 100644 index 0000000..00abc6d Binary files /dev/null and b/res/sounds/effects/gear1.ogg differ diff --git a/res/sounds/effects/gear2.ogg b/res/sounds/effects/gear2.ogg new file mode 100644 index 0000000..08a7385 Binary files /dev/null and b/res/sounds/effects/gear2.ogg differ diff --git a/res/sounds/effects/loop_drag.ogg b/res/sounds/effects/loop_drag.ogg new file mode 100644 index 0000000..9435ab4 Binary files /dev/null and b/res/sounds/effects/loop_drag.ogg differ diff --git a/res/sounds/effects/loop_walking.ogg b/res/sounds/effects/loop_walking.ogg new file mode 100644 index 0000000..f46adc9 Binary files /dev/null and b/res/sounds/effects/loop_walking.ogg differ diff --git a/res/sounds/effects/popup.ogg b/res/sounds/effects/popup.ogg new file mode 100644 index 0000000..0cebca0 Binary files /dev/null and b/res/sounds/effects/popup.ogg differ diff --git a/res/sounds/effects/shutter.ogg b/res/sounds/effects/shutter.ogg new file mode 100644 index 0000000..c54d622 Binary files /dev/null and b/res/sounds/effects/shutter.ogg differ diff --git a/res/sounds/effects/splash.ogg b/res/sounds/effects/splash.ogg new file mode 100644 index 0000000..a47eb03 Binary files /dev/null and b/res/sounds/effects/splash.ogg differ diff --git a/res/sounds/effects/turtle_drop.ogg b/res/sounds/effects/turtle_drop.ogg new file mode 100644 index 0000000..0bb34f6 Binary files /dev/null and b/res/sounds/effects/turtle_drop.ogg differ diff --git a/res/sounds/effects/waterdrops/drop1.ogg b/res/sounds/effects/waterdrops/drop1.ogg new file mode 100644 index 0000000..450d5f1 Binary files /dev/null and b/res/sounds/effects/waterdrops/drop1.ogg differ diff --git a/res/sounds/effects/waterdrops/drop2.ogg b/res/sounds/effects/waterdrops/drop2.ogg new file mode 100644 index 0000000..94987b7 Binary files /dev/null and b/res/sounds/effects/waterdrops/drop2.ogg differ diff --git a/res/sounds/effects/waterdrops/drop3.ogg b/res/sounds/effects/waterdrops/drop3.ogg new file mode 100644 index 0000000..8ecb903 Binary files /dev/null and b/res/sounds/effects/waterdrops/drop3.ogg differ diff --git a/res/sounds/effects/waterdrops/drop4.ogg b/res/sounds/effects/waterdrops/drop4.ogg new file mode 100644 index 0000000..c51d311 Binary files /dev/null and b/res/sounds/effects/waterdrops/drop4.ogg differ diff --git a/res/sounds/effects/waterdrops/drop5.ogg b/res/sounds/effects/waterdrops/drop5.ogg new file mode 100644 index 0000000..2a9b53a Binary files /dev/null and b/res/sounds/effects/waterdrops/drop5.ogg differ diff --git a/res/sounds/effects/waterdrops/drop6.ogg b/res/sounds/effects/waterdrops/drop6.ogg new file mode 100644 index 0000000..feb2e84 Binary files /dev/null and b/res/sounds/effects/waterdrops/drop6.ogg differ diff --git a/res/sounds/loops/crate_move.ogg b/res/sounds/loops/crate_move.ogg new file mode 100644 index 0000000..9435ab4 Binary files /dev/null and b/res/sounds/loops/crate_move.ogg differ diff --git a/res/sounds/loops/rain.ogg b/res/sounds/loops/rain.ogg new file mode 100644 index 0000000..12b469c Binary files /dev/null and b/res/sounds/loops/rain.ogg differ diff --git a/res/sounds/loops/stream.ogg b/res/sounds/loops/stream.ogg new file mode 100644 index 0000000..c3e028c Binary files /dev/null and b/res/sounds/loops/stream.ogg differ diff --git a/res/sounds/loops/turtle_walking.ogg b/res/sounds/loops/turtle_walking.ogg new file mode 100644 index 0000000..f46adc9 Binary files /dev/null and b/res/sounds/loops/turtle_walking.ogg differ diff --git a/res/sounds/loops/wilderness.ogg b/res/sounds/loops/wilderness.ogg new file mode 100644 index 0000000..9911911 Binary files /dev/null and b/res/sounds/loops/wilderness.ogg differ diff --git a/src/com/porcupine/color/HSV.java b/src/com/porcupine/color/HSV.java new file mode 100644 index 0000000..6ece6c5 --- /dev/null +++ b/src/com/porcupine/color/HSV.java @@ -0,0 +1,177 @@ +package com.porcupine.color; + + +import java.awt.Color; + +import com.porcupine.math.Calc; + + +/** + * HSV color + * + * @author MightyPork + */ +public class HSV { + + /** H */ + public double h; + /** S */ + public double s; + /** V */ + public double v; + + + /** + * Create black color 0,0,0 + */ + public HSV() {} + + + /** + * Color from HSV 0-1 + * + * @param h + * @param s + * @param v + */ + public HSV(Number h, Number s, Number v) { + this.h = h.doubleValue(); + this.s = s.doubleValue(); + this.v = v.doubleValue(); + norm(); + } + + + /** + * @return hue 0-1 + */ + public double h() + { + return h; + } + + + /** + * @return saturation 0-1 + */ + public double s() + { + return s; + } + + + /** + * @return value/brightness 0-1 + */ + public double v() + { + return v; + } + + + /** + * Set color to other color + * + * @param copied copied color + * @return this + */ + public HSV setTo(HSV copied) + { + h = copied.h; + s = copied.s; + v = copied.v; + + norm(); + return this; + } + + + /** + * Set to H,S,V 0-1 + * + * @param h hue + * @param s saturation + * @param v value + * @return this + */ + public HSV setTo(Number h, Number s, Number v) + { + this.h = h.doubleValue(); + this.s = s.doubleValue(); + this.v = v.doubleValue(); + norm(); + return this; + } + + + /** + * Fix numbers out of range 0-1 + */ + public void norm() + { + h = Calc.clampd(h, 0, 1); + s = Calc.clampd(s, 0, 1); + v = Calc.clampd(v, 0, 1); + } + + + /** + * Convert to RGB + * + * @return RGB representation + */ + public RGB toRGB() + { + norm(); + + int rgb = Color.HSBtoRGB((float) h, (float) s, (float) v); + + return RGB.fromHex(rgb); + } + + + /** + * Make from RGB + * + * @param color RGB + * @return HSV + */ + public static HSV fromRGB(RGB color) + { + return color.toHSV(); + } + + + @Override + public String toString() + { + return "HSV[" + h + ";" + s + ";" + v + "]"; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof HSV)) return false; + return ((HSV) obj).h == h && ((HSV) obj).s == s && ((HSV) obj).v == v; + } + + + @Override + public int hashCode() + { + return Double.valueOf(h).hashCode() ^ Double.valueOf(s).hashCode() ^ Double.valueOf(v).hashCode(); + } + + + /** + * Get a copy + * + * @return copy + */ + public HSV copy() + { + return new HSV().setTo(this); + } +} diff --git a/src/com/porcupine/color/RGB.java b/src/com/porcupine/color/RGB.java new file mode 100644 index 0000000..357c2da --- /dev/null +++ b/src/com/porcupine/color/RGB.java @@ -0,0 +1,382 @@ +package com.porcupine.color; + + +import java.awt.Color; + +import com.porcupine.math.Calc; + + +/** + * RGB color + * + * @author MightyPork + */ +public class RGB { + + /** White */ + public static final RGB WHITE = new RGB(1, 1, 1); + /** Black */ + public static final RGB BLACK = new RGB(0, 0, 0); + /** Red */ + public static final RGB RED = new RGB(1, 0, 0); + /** Lime green */ + public static final RGB GREEN = new RGB(0, 1, 0); + /** Blue */ + public static final RGB BLUE = new RGB(0, 0, 1); + /** Yellow */ + public static final RGB YELLOW = new RGB(1, 1, 0); + /** Purple */ + public static final RGB PURPLE = new RGB(1, 0, 1); + /** Cyan */ + public static final RGB CYAN = new RGB(0, 1, 1); + /** orange */ + public static final RGB ORANGE = new RGB(1, 0.6, 0); + /** no color (alpha=0) */ + public static final RGB TRANSPARENT = new RGB(0, 0, 0, 0); + + /** R */ + public double r; + /** G */ + public double g; + /** B */ + public double b; + /** ALPHA */ + public double a = 1; + + + /** + * Create black color 0,0,0 + */ + public RGB() {} + + + /** + * Get copy with custom alpha + * + * @param alpha alpha to set + * @return copy w/ alpha + */ + public RGB setAlpha(double alpha) + { + return copy().setAlpha_ip(alpha); + } + + + /** + * set alpha IP + * + * @param alpha alpha to set + * @return this + */ + public RGB setAlpha_ip(double alpha) + { + a = alpha; + norm(); + return this; + } + + + /** + * Get copy. + * + * @return copy + */ + public RGB copy() + { + return new RGB(r, g, b, a); + } + + + /** + * Get copy with alpha multiplied by custom value + * + * @param alpha alpha to set + * @return copy w/ alpha + */ + public RGB mulAlpha(double alpha) + { + return copy().mulAlpha_ip(alpha); + } + + + /** + * Multiply alpha by given number + * + * @param alpha alpha multiplier + * @return this + */ + public RGB mulAlpha_ip(double alpha) + { + a *= alpha; + norm(); + return this; + } + + + /** + * Color from RGB 0-1 + * + * @param r red + * @param g green + * @param b blue + */ + public RGB(Number r, Number g, Number b) { + this.r = r.doubleValue(); + this.g = g.doubleValue(); + this.b = b.doubleValue(); + norm(); + } + + + /** + * Color from RGB 0-1 + * + * @param r red + * @param g green + * @param b blue + * @param a alpha + */ + public RGB(Number r, Number g, Number b, Number a) { + this.r = r.doubleValue(); + this.g = g.doubleValue(); + this.b = b.doubleValue(); + this.a = a.doubleValue(); + norm(); + } + + + /** + * Color from hex 0xRRGGBB + * + * @param hex hex integer + */ + public RGB(int hex) { + setTo(RGB.fromHex(hex)); + norm(); + } + + + /** + * Color from hex 0xRRGGBB + * + * @param hex hex integer + * @param alpha alpha color + */ + public RGB(int hex, double alpha) { + setTo(RGB.fromHex(hex)); + a = alpha; + norm(); + } + + + /** + * Color from other RGB and alpha channel + * + * @param color other RGB color + * @param alpha new alpha channel + */ + public RGB(RGB color, double alpha) { + setTo(color); + setAlpha_ip(alpha); + } + + + /** + * @return red channel 0-1 + */ + public double r() + { + return r; + } + + + /** + * @return green channel 0-1 + */ + public double g() + { + return g; + } + + + /** + * @return blue channel 0-1 + */ + public double b() + { + return b; + } + + + /** + * @return alpha 0-1 + */ + public double a() + { + return a; + } + + + /** + * Set color to other color + * + * @param copied copied color + * @return this + */ + public RGB setTo(RGB copied) + { + r = copied.r; + g = copied.g; + b = copied.b; + a = copied.a; + + norm(); + return this; + } + + + /** + * Set to represent hex color + * + * @param hex hex integer RRGGBB + * @return this + */ + public RGB setTo(int hex) + { + setTo(RGB.fromHex(hex)); + norm(); + return this; + } + + + /** + * Set to R,G,B 0-1 + * + * @param r red + * @param g green + * @param b blue + * @param a alpha + * @return this + */ + public RGB setTo(Number r, Number g, Number b, Number a) + { + this.r = r.doubleValue(); + this.g = g.doubleValue(); + this.b = b.doubleValue(); + this.a = a.doubleValue(); + norm(); + return this; + } + + + /** + * Set to R,G,B 0-1 + * + * @param r red + * @param g green + * @param b blue + * @return this + */ + public RGB setTo(Number r, Number g, Number b) + { + this.r = r.doubleValue(); + this.g = g.doubleValue(); + this.b = b.doubleValue(); + this.a = 1; + norm(); + return this; + } + + + /** + * Fix numbers out of range 0-1 + * + * @return this + */ + public RGB norm() + { + r = Calc.clampd(r, 0, 1); + g = Calc.clampd(g, 0, 1); + b = Calc.clampd(b, 0, 1); + a = Calc.clampd(a, 0, 1); + return this; + } + + + /** + * Get hex value 0xRRGGBB + * + * @return hex value RRGGBB + */ + public int getHex() + { + int ri = (int) Math.round(r * 255); + int gi = (int) Math.round(g * 255); + int bi = (int) Math.round(b * 255); + return (ri << 16) | (gi << 8) | bi; + } + + + /** + * Convert to HSV + * + * @return HSV representation + */ + public HSV toHSV() + { + float[] hsv = { 0, 0, 0 }; + Color.RGBtoHSB((int) (r * 255), (int) (g * 255), (int) (b * 255), hsv); + return new HSV(hsv[0], hsv[1], hsv[2]); + } + + + /** + * Create color from hex 0xRRGGBB + * + * @param hex hex RRGGBB + * @return the new color + */ + public static RGB fromHex(int hex) + { + int bi = hex & 0xff; + int gi = (hex >> 8) & 0xff; + int ri = (hex >> 16) & 0xff; + return new RGB(ri / 255D, gi / 255D, bi / 255D); + } + + + /** + * Make from HSV + * + * @param color HSV color + * @return RGB + */ + public static RGB fromHSV(HSV color) + { + return color.toRGB(); + } + + + @Override + public String toString() + { + return "RGB[" + r + ";" + g + ";" + b + ";" + a + "]"; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof RGB)) return false; + return ((RGB) obj).r == r && ((RGB) obj).g == g && ((RGB) obj).b == b && ((RGB) obj).a == a; + } + + + @Override + public int hashCode() + { + return Double.valueOf(r).hashCode() ^ Double.valueOf(g).hashCode() ^ Double.valueOf(b).hashCode() ^ Double.valueOf(a).hashCode(); + } + +} diff --git a/src/com/porcupine/coord/Coord.java b/src/com/porcupine/coord/Coord.java new file mode 100644 index 0000000..cc7812d --- /dev/null +++ b/src/com/porcupine/coord/Coord.java @@ -0,0 +1,930 @@ +package com.porcupine.coord; + + +import java.util.Random; + +import com.porcupine.math.Calc; + + +/** + * Coordinate class, object with three or two double coordinates.
+ * + * @author MightyPork + */ +public class Coord { + + /** Coord [1;1;1] */ + public static final Coord ONE = new Coord(1, 1, 1); + + /** Zero Coord */ + public static final Coord ZERO = new Coord(0, 0); + + /** RNG */ + protected static Random rand = new Random(); + + + /** + * Get distance to other point + * + * @param a point a + * @param b point b + * @return distance in units + */ + public static double dist(Coord a, Coord b) + { + return a.distTo(b); + } + + + /** + * Generate random coord (gaussian) + * + * @param max max distance from 0 + * @return new coord + */ + public static Coord random(double max) + { + return new Coord(Calc.clampd(rand.nextGaussian() * max, -max * 2, max * 2), Calc.clampd(rand.nextGaussian() * max, -max * 2, max * 2), + Calc.clampd(rand.nextGaussian() * max, -max * 2, max * 2)); + } + + + /** + * Generate random coord (min-max) + * + * @param min min offset + * @param max max offset + * @return new coord + */ + public static Coord random(double min, double max) + { + return new Coord((rand.nextBoolean() ? -1 : 1) * (min + rand.nextDouble() * (max - min)), (rand.nextBoolean() ? -1 : 1) * (min + rand.nextDouble() * (max - min)), + (rand.nextBoolean() ? -1 : 1) * (min + rand.nextDouble() * (max - min))); + } + + private double animTime = 0; + + private Vec offs; + + private Coord start; + + private double time = 0; + + /** X coordinate */ + public double x = 0; + + /** Y coordinate */ + public double y = 0; + + /** Z coordinate */ + public double z = 0; + + + /** + * Create zero coord + */ + public Coord() {} + + + /** + * Create coord as a copy of another + * + * @param copied copied coord + */ + public Coord(Coord copied) { + this.x = copied.x; + this.y = copied.y; + this.z = copied.z; + } + + + /** + * Create 2D coord + * + * @param x x coordinate + * @param y y coordinate + */ + public Coord(Number x, Number y) { + setTo(x, y); + } + + + /** + * Create 3D coord + * + * @param x x coordinate + * @param y y coordinate + * @param z z coordinate + */ + public Coord(Number x, Number y, Number z) { + setTo(x, y, z); + } + + + /** + * Get a copy offset by vector + * + * @param vec offset + * @return the offset copy + */ + public Coord add(Coord vec) + { + return copy().add_ip(vec); + } + + + /** + * Get a copy offset by 2D coordinate + * + * @param x x offset + * @param y y offset + * @return the offset copy + */ + public Coord add(Number x, Number y) + { + return copy().add_ip(x, y); + } + + + /** + * Get a copy offset by 3D coordinate + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return the offset copy + */ + public Coord add(Number x, Number y, Number z) + { + return copy().add_ip(x, y, z); + } + + + /** + * Offset by vector in place + * + * @param vec offset + * @return this + */ + public Coord add_ip(Coord vec) + { + this.x += vec.x; + this.y += vec.y; + this.z += vec.z; + return this; + } + + + /** + * Offset by 2D coordinate in place + * + * @param x x offset + * @param y y offset + * @return this + */ + public Coord add_ip(Number x, Number y) + { + this.x += x.doubleValue(); + this.y += y.doubleValue(); + return this; + } + + + /** + * Offset by 3D coordinate in place + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return this + */ + public Coord add_ip(Number x, Number y, Number z) + { + this.x += x.doubleValue(); + this.y += y.doubleValue(); + this.z += z.doubleValue(); + return this; + } + + + /** + * Start animation + * + * @param time anim length + */ + public void animate(double time) + { + if (start == null) start = new Coord(); + if (offs == null) offs = new Vec(); + this.time = time; + animTime = 0; + offs = start.vecTo(this); + } + + + /** + * @return copy of this vector + */ + public Coord copy() + { + return new Coord(x, y, z); + } + + + /** + * Get distance to other point + * + * @param point other point + * @return distance in units + */ + public double distTo(Coord point) + { + return Math.sqrt((point.x - x) * (point.x - x) + (point.y - y) * (point.y - y) + (point.z - z) * (point.z - z)); + } + + + /** + * Get copy divided by number + * + * @param d number to divide by + * @return divided copy + */ + public Coord div(double d) + { + return copy().div_ip(d); + } + + + /** + * Divide by number in place + * + * @param d number to divide by + * @return this + */ + public Coord div_ip(double d) + { + if (d == 0) return this; + x /= d; + y /= d; + z /= d; + return this; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!obj.getClass().isAssignableFrom(Coord.class)) return false; + Coord other = (Coord) obj; + return x == other.x && y == other.y && z == other.z; + } + + + /** + * Get current value (animated) + * + * @return curent value + */ + public Coord getDelta() + { + if (start == null) start = new Coord(); + if (offs == null) offs = new Vec(); + if (isFinished()) return this; + return new Coord(start.add(offs.scale(animTime / time))); + } + + + @Override + public int hashCode() + { + return Double.valueOf(x).hashCode() ^ Double.valueOf(y).hashCode() ^ Double.valueOf(z).hashCode(); + } + + + /** + * Get if animation is finished + * + * @return is finished + */ + public boolean isFinished() + { + return animTime >= time; + } + + + /** + * Check if this rectangle in inside a rectangular zone + * + * @param min min coord + * @param max max coord + * @return is inside + */ + public boolean isInRect(Coord min, Coord max) + { + return (x >= min.x && x <= max.x) && (y >= min.y && y <= max.y) && (z >= min.z && z <= max.z); + } + + + /** + * Check if this rectangle in inside a rectangular zone + * + * @param rect checked rect. + * @return is inside + */ + public boolean isInRect(Rect rect) + { + return isInRect(rect.min, rect.max); + } + + + /** + * Get middle of line to other point + * + * @param other other point + * @return middle + */ + public Coord midTo(Coord other) + { + return add(vecTo(other).scale(0.5)); + } + + + /** + * Multiply by number + * + * @param d number + * @return multiplied copy + */ + public Coord mul(double d) + { + return copy().mul_ip(d); + } + + + /** + * Multiply coords by number + * + * @param xd x multiplier + * @param yd y multiplier + * @param zd z multiplier + * @return multiplied copy + */ + public Coord mul(double xd, double yd, double zd) + { + return copy().mul_ip(xd, yd, zd); + } + + + /** + * Multiply by number in place + * + * @param d multiplier + * @return this + */ + public Coord mul_ip(double d) + { + x *= d; + y *= d; + z *= d; + return this; + } + + + /** + * Multiply coords by number in place + * + * @param xd x multiplier + * @param yd y multiplier + * @param zd z multiplier + * @return this + */ + public Coord mul_ip(double xd, double yd, double zd) + { + x *= xd; + y *= yd; + z *= zd; + return this; + } + + + /** + * offset randomly + * + * @param max max +- offset + * @return offset coord + */ + public Coord random_offset(double max) + { + Coord r = random(1); + Vec v = new Vec(r); + v.norm_ip(0.00001 + rand.nextDouble() * max); + return copy().add_ip(v); + } + + + /** + * offset randomly + * + * @param min min offset + * @param max max offset + * @return offset coord + */ + public Coord random_offset(double min, double max) + { + return copy().add_ip(random(min, max)); + } + + + /** + * offset randomly in place + * + * @param max max +- offset + * @return this + */ + public Coord random_offset_ip(double max) + { + return add(random(max)); + } + + + /** + * offset randomly in place + * + * @param min min offset + * @param max max offset + * @return this + */ + public Coord random_offset_ip(double min, double max) + { + add(random(min, max)); + return this; + } + + + /** + * Remember position (other changes will be for animation) + */ + public void remember() + { + if (start == null) start = new Coord(); + if (offs == null) offs = new Vec(); + start.setTo(this); + } + + + /** + * Get a copy with rounded coords + * + * @return rounded copy + */ + public Coord round() + { + return copy().round_ip(); + } + + + /** + * Round in place + * + * @return this + */ + public Coord round_ip() + { + x = Math.round(x); + y = Math.round(y); + z = Math.round(z); + return this; + } + + + /** + * Set to max values of this and other coord + * + * @param other other coord + */ + public void setMax(Coord other) + { + x = Math.max(x, other.x); + y = Math.max(y, other.y); + z = Math.max(z, other.z); + } + + + /** + * Set to min values of this and other coord + * + * @param other other coord + */ + public void setMin(Coord other) + { + x = Math.min(x, other.x); + y = Math.min(y, other.y); + z = Math.min(z, other.z); + } + + + /** + * Set coordinates to match other coord + * + * @param copied coord whose coordinates are used + * @return this + */ + public Coord setTo(Coord copied) + { + setTo(copied.x, copied.y, copied.z); + return this; + } + + + /** + * Set 2D coordinates to + * + * @param x x coordinate + * @param y y coordinate + * @return this + */ + public Coord setTo(Number x, Number y) + { + setTo(x, y, 0); + return this; + } + + + /** + * Set 3D coordinates to + * + * @param x x coordinate + * @param y y coordinate + * @param z z coordinate + * @return this + */ + public Coord setTo(Number x, Number y, Number z) + { + this.x = x.doubleValue(); + this.y = y.doubleValue(); + this.z = z.doubleValue(); + return this; + } + + + /** + * Set X coordinate in a copy + * + * @param x x coordinate + * @return copy with set coordinate + */ + public Coord setX(Number x) + { + return copy().setX_ip(x); + } + + + /** + * Set X coordinate in place + * + * @param x x coordinate + * @return this + */ + public Coord setX_ip(Number x) + { + this.x = x.doubleValue(); + return this; + } + + + /** + * Set Y coordinate in a copy + * + * @param y y coordinate + * @return copy with set coordinate + */ + public Coord setY(Number y) + { + return copy().setY_ip(y); + } + + + /** + * Set Y coordinate in place + * + * @param y y coordinate + * @return this + */ + public Coord setY_ip(Number y) + { + this.y = y.doubleValue(); + return this; + } + + + /** + * Set Z coordinate in a copy + * + * @param z z coordinate + * @return copy with set coordinate + */ + public Coord setZ(Number z) + { + return copy().setZ_ip(z); + } + + + /** + * Set Z coordinate in place + * + * @param z z coordinate + * @return this + */ + public Coord setZ_ip(Number z) + { + this.z = z.doubleValue(); + return this; + } + + + /** + * Get size + * + * @return size + */ + public double size() + { + return new Vec(this).size(); + } + + + /** + * Get a copy subtracted by vector + * + * @param vec offset + * @return the offset copy + */ + public Coord sub(Coord vec) + { + return copy().sub_ip(vec); + } + + + /** + * Get a copy subtracted by 2D coordinate + * + * @param x x offset + * @param y y offset + * @return the offset copy + */ + public Coord sub(Number x, Number y) + { + return copy().sub_ip(x, y); + } + + + /** + * Get a copy subtracted by 3D coordinate + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return the offset copy + */ + public Coord sub(Number x, Number y, Number z) + { + return copy().sub_ip(x, y, z); + } + + + /** + * Offset by vector in place + * + * @param vec offset + * @return this + */ + public Coord sub_ip(Coord vec) + { + this.x -= vec.x; + this.y -= vec.y; + this.z -= vec.z; + return this; + } + + + /** + * Offset by 2D coordinate in place + * + * @param x x offset + * @param y y offset + * @return this + */ + public Coord sub_ip(Number x, Number y) + { + this.x -= x.doubleValue(); + this.y -= y.doubleValue(); + return this; + } + + + /** + * Offset by 3D coordinate in place + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return this + */ + public Coord sub_ip(Number x, Number y, Number z) + { + this.x -= x.doubleValue(); + this.y -= y.doubleValue(); + this.z -= z.doubleValue(); + return this; + } + + + /** + * Convert X and Y coordinates of this coord to a new CoordI. + * + * @return the new CoordI + */ + public CoordI toCoordI() + { + return new CoordI((int) Math.round(x), (int) Math.round(y)); + } + + + @Override + public String toString() + { + return "[ " + x + " ; " + y + " ; " + z + " ]"; + } + + +// private Coord last; +// private Vec offs; +// +// /** +// * Store current value as LAST +// */ +// public void pushLast() { +// if (last == null) last = new Coord(); +// if (offs == null) offs = new Vec(); +// last.setTo(this); +// } +// +// /** +// * Apply coordinates change for delta time +// */ +// public void update() { +// if (last == null) last = new Coord(); +// if (offs == null) offs = new Vec(); +// offs = last.vecTo(this); +// } +// +// /** +// * Get coordinate at delta time since LAST +// * +// * @param delta delta time 0-1 +// * @return delta pos +// */ +// public Coord getDelta(double delta) { +// if (last == null) last = new Coord(); +// if (offs == null) offs = new Vec(); +// return new Coord(this.add(offs.scale(delta))); +// } + + /** + * Update delta timing + * + * @param delta delta time to add + */ + public void update(double delta) + { + if (start == null) start = new Coord(); + if (offs == null) offs = new Vec(); + animTime = Calc.clampd(animTime + delta, 0, time); + if (isFinished()) { + time = 0; + animTime = 0; + start.setTo(this); + } + } + + + /** + * Create vector from this point to other point + * + * @param point second point + * @return vector + */ + public Vec vecTo(Coord point) + { + return (Vec) (new Vec(point)).add(new Vec(this).neg()); + } + + + /** + * @return X as double + */ + public double x() + { + return x; + } + + + /** + * @return X as double + */ + public double xd() + { + return x; + } + + + /** + * @return X as float + */ + public float xf() + { + return (float) x; + } + + + /** + * @return X as int + */ + public int xi() + { + return (int) Math.round(x); + } + + + /** + * @return Y as double + */ + public double y() + { + return y; + } + + + /** + * @return Y as double + */ + public double yd() + { + return y; + } + + + /** + * @return Y as float + */ + public float yf() + { + return (float) y; + } + + + /** + * @return Y as int + */ + public int yi() + { + return (int) Math.round(y); + } + + + /** + * @return Z as double + */ + public double z() + { + return z; + } + + + /** + * @return Z as double + */ + public double zd() + { + return z; + } + + + /** + * @return Z as float + */ + public float zf() + { + return (float) z; + } + + + /** + * @return Z as int + */ + public int zi() + { + return (int) Math.round(z); + } +} diff --git a/src/com/porcupine/coord/CoordI.java b/src/com/porcupine/coord/CoordI.java new file mode 100644 index 0000000..c8b3584 --- /dev/null +++ b/src/com/porcupine/coord/CoordI.java @@ -0,0 +1,398 @@ +package com.porcupine.coord; + + +/** + * Simple integer coordinate class
+ * Unlike Coord, this is suitable for using in array indices etc. + * + * @author MightyPork + */ +public class CoordI { + + /** X coordinate */ + public int x = 0; + /** Y coordinate */ + public int y = 0; + /** Z coordinate */ + public int z = 0; + + + /** + * Create CoordI as copy of other + * + * @param other coord to copy + */ + public CoordI(CoordI other) { + setTo(other); + } + + + /** + * Integer 2D Coord + * + * @param x x coord + * @param y y coord + */ + public CoordI(int x, int y) { + this.x = x; + this.y = y; + } + + + /** + * Integer 3D Coord + * + * @param x x coord + * @param y y coord + * @param z z coord + */ + public CoordI(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + + /** + * Empty cobnstructor 0,0,0 + */ + public CoordI() { + x = 0; + y = 0; + z = 0; + } + + + /** + * Add other coordI coordinates in a copy + * + * @param other coordI to add + * @return copy modified + */ + public CoordI add(CoordI other) + { + return copy().add_ip(other); + } + + + /** + * Add coords in copy + * + * @param x x coord + * @param y y coord + * @return the copy + */ + public CoordI add(int x, int y) + { + return copy().add_ip(x, y, 0); + } + + + /** + * Add coords in copy + * + * @param x x coord + * @param y y coord + * @param z z coord + * @return the copy + */ + public CoordI add(int x, int y, int z) + { + return copy().add_ip(x, y, z); + } + + + /** + * Add other coordI coordinates in place + * + * @param move coordI to add + * @return this + */ + public CoordI add_ip(CoordI move) + { + x += move.x; + y += move.y; + z += move.z; + return this; + } + + + /** + * Add coords in place + * + * @param x x coord + * @param y y coord + * @return this + */ + public CoordI add_ip(int x, int y) + { + this.x += x; + this.y += y; + return this; + } + + + /** + * Add coords in place + * + * @param x x coord + * @param y y coord + * @param z z coord + * @return this + */ + public CoordI add_ip(int x, int y, int z) + { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + + /** + * Get copy + * + * @return copy + */ + public CoordI copy() + { + return new CoordI(x, y, z); + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) return false; + if (obj instanceof CoordI) return ((CoordI) obj).x == x && ((CoordI) obj).y == y && ((CoordI) obj).z == z; + return false; + } + + + @Override + public int hashCode() + { + return x ^ y ^ z; + } + + + /** + * Middle of this and other coordinate, rounded to CoordI - integers + * + * @param other other coordI + * @return middle CoordI + */ + public CoordI midTo(CoordI other) + { + return new CoordI((x + other.x) / 2, (y + other.y) / 2, (z + other.z) / 2); + } + + + /** + * Multiply in copy 2D + * + * @param x x coord + * @param y y coord + * @return the copy + */ + public CoordI mul(double x, double y) + { + return copy().mul_ip(x, y); + } + + + /** + * Multiply in copy + * + * @param x x coord + * @param y y coord + * @param z z coord + * @return the copy + */ + public CoordI mul(double x, double y, double z) + { + return copy().mul_ip(x, y, z); + } + + + /** + * Multiply in place 2D + * + * @param x x coord + * @param y y coord + * @return this + */ + public CoordI mul_ip(double x, double y) + { + this.x *= x; + this.y *= y; + return this; + } + + + /** + * Multiply in place + * + * @param x x coord + * @param y y coord + * @param z z coord + * @return this + */ + public CoordI mul_ip(double x, double y, double z) + { + this.x *= x; + this.y *= y; + this.z *= z; + return this; + } + + + /** + * Set to coords from other coord + * + * @param other source coord + */ + public void setTo(CoordI other) + { + if (other == null) { + setTo(0, 0, 0); + return; + } + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + + /** + * Set coords to + * + * @param x x coord to set + * @param y y coord to set + */ + public void setTo(int x, int y) + { + this.x = x; + this.y = y; + } + + + /** + * Set coords to + * + * @param x x coord to set + * @param y y coord to set + * @param z z coord to set + */ + public void setTo(int x, int y, int z) + { + this.x = x; + this.y = y; + this.z = z; + } + + + /** + * Subtract other coordI coordinates in a copy + * + * @param other coordI to subtract + * @return copy subtracted + */ + public CoordI sub(CoordI other) + { + return copy().sub_ip(other); + } + + + /** + * Subtract x,y in a copy + * + * @param x x to subtract + * @param y y to subtract + * @return copy subtracted + */ + public CoordI sub(int x, int y) + { + return copy().sub_ip(new CoordI(x, y)); + } + + + /** + * Subtract x,y,z in a copy + * + * @param x x to subtract + * @param y y to subtract + * @param z z to subtract + * @return copy subtracted + */ + public CoordI sub(int x, int y, int z) + { + return copy().sub_ip(new CoordI(x, y, z)); + } + + + /** + * Subtract other coordI coordinates in place + * + * @param move coordI to subtract + * @return this + */ + public CoordI sub_ip(CoordI move) + { + x -= move.x; + y -= move.y; + z -= move.z; + return this; + } + + + /** + * Sub coords in place + * + * @param x x coord + * @param y y coord + * @return this + */ + public CoordI sub_ip(int x, int y) + { + this.x -= x; + this.y -= y; + return this; + } + + + /** + * Sub coords in place + * + * @param x x coord + * @param y y coord + * @param z z coord + * @return this + */ + public CoordI sub_ip(int x, int y, int z) + { + this.x -= x; + this.y -= y; + this.z -= z; + return this; + } + + + /** + * Convert to double Coord + * + * @return coord with X and y from this CoordI + */ + public Coord toCoord() + { + return new Coord(x, y); + } + + + @Override + public String toString() + { + return "[ " + x + " ; " + y + " ; " + z + " ]"; + } + +} diff --git a/src/com/porcupine/coord/Rect.java b/src/com/porcupine/coord/Rect.java new file mode 100644 index 0000000..c31b932 --- /dev/null +++ b/src/com/porcupine/coord/Rect.java @@ -0,0 +1,805 @@ +package com.porcupine.coord; + + +import com.porcupine.math.Calc; + + +/** + * Rectangle determined by two coordinates - min and max. + * + * @author MightyPork + */ +public class Rect { + + /** Rect [0, 0, 1, 1] */ + public static final Rect ONE = new Rect(0, 0, 1, 1); + /** Rect all zeros */ + public static final Rect ZERO = new Rect(0, 0, 0, 0); + + + /** + * Rectangle from size + * + * @param min min coord + * @param size rect size + * @return the rect + */ + public static Rect fromSize(Coord min, Coord size) + { + return fromSize(min, size.xi(), size.yi()); + } + + + /** + * Make rect from min coord and size + * + * @param min min coord + * @param width size x + * @param height size y + * @return the new rect + */ + public static Rect fromSize(Coord min, int width, int height) + { + return new Rect(min, min.add(width, height)); + } + + + /** + * Rectangle from size + * + * @param i min X + * @param j min Y + * @param size rect size + * @return the rect + */ + public static Rect fromSize(int i, int j, CoordI size) + { + return fromSize(i, j, size.x, size.y); + } + + + /** + * Make rect from min coord and size + * + * @param x1 min x + * @param y1 min y + * @param width size x + * @param height size y + * @return the new rect + */ + public static Rect fromSize(int x1, int y1, int width, int height) + { + return new Rect(x1, y1, x1 + width, y1 + height); + } + + /** Highest coordinates xy */ + protected Coord max = new Coord(); + + /** Lowest coordinates xy */ + protected Coord min = new Coord(); + + + /** + * New Rect [0, 0, 0, 0] + */ + public Rect() { + this(0, 0, 0, 0); + } + + + /** + * Rect [0, 0, size.x, size.y] + * + * @param size size coord + */ + public Rect(Coord size) { + this(0, 0, size.x, size.y); + } + + + /** + * New rect of two coords + * + * @param c1 coord 1 + * @param c2 coord 2 + */ + public Rect(Coord c1, Coord c2) { + this(c1.x, c1.y, c2.x, c2.y); + } + + + /** + * New Rect + * + * @param x1 lower x + * @param y1 lower y + * @param x2 upper x + * @param y2 upper y + */ + public Rect(double x1, double y1, double x2, double y2) { + setTo(x1, y1, x2, y2); + } + + + /** + * Rect [0, 0, x, y] + * + * @param x width + * @param y height + */ + public Rect(int x, int y) { + this(0, 0, x, y); + } + + + /** + * New rect as a copy of other rect + * + * @param r other rect + */ + public Rect(Rect r) { + this(r.min.x, r.min.y, r.max.x, r.max.y); + } + + + /** + * Get offset copy (add) + * + * @param move offset vector + * @return offset copy + */ + public Rect add(Coord move) + { + return copy().add_ip(move); + } + + + /** + * Add X and Y to all coordinates in a copy + * + * @param x x to add + * @param y y to add + * @return copy changed + */ + public Rect add(double x, double y) + { + return add(new Vec(x, y)); + } + + + /** + * Offset in place (add) + * + * @param move offset vector + * @return this + */ + public Rect add_ip(Coord move) + { + min.add_ip(move); + max.add_ip(move); + return this; + } + + + /** + * Add X and Y to all coordinates in place + * + * @param x x to add + * @param y y to add + * @return this + */ + public Rect add_ip(double x, double y) + { + return add_ip(new Vec(x, y)); + } + + + /** + * Get a copy + * + * @return copy + */ + public Rect copy() + { + return new Rect(this); + } + + + /** + * Divide in copy + * + * @param factor divisor + * @return offset copy + */ + public Rect div(double factor) + { + return copy().div_ip(factor); + } + + + /** + * Divide coord in place + * + * @param factor divisor + * @return this + */ + public Rect div_ip(double factor) + { + min.div_ip(factor); + max.div_ip(factor); + return this; + } + + + /** + * Get copy with the same center and height=0 + * + * @return line + */ + public Rect getAxisH() + { + return new Rect(getCenterLeft(), getCenterRight()); + } + + + /** + * Get copy with the same center and width=0 + * + * @return line + */ + public Rect getAxisV() + { + return new Rect(getCenterDown(), getCenterTop()); + } + + + /** + * Get rect center + * + * @return center + */ + public Coord getCenter() + { + return min.midTo(max); + } + + + /** + * Get center of the lower edge. + * + * @return center + */ + public Coord getCenterDown() + { + return new Coord((max.x + min.x) / 2, min.y); + } + + + /** + * Get center of the left edge. + * + * @return center + */ + public Coord getCenterLeft() + { + return new Coord(min.x, (max.y + min.y) / 2); + } + + + /** + * Get center of the right edge. + * + * @return center + */ + public Coord getCenterRight() + { + return new Coord(max.x, (max.y + min.y) / 2); + } + + + /** + * Get center of the top edge. + * + * @return center + */ + public Coord getCenterTop() + { + return new Coord((max.x + min.x) / 2, max.y); + } + + + /** + * Get bottom edge rect + * + * @return line + */ + public Rect getEdgeBottom() + { + return new Rect(getLeftBottom(), getRightBottom()); + } + + + /** + * Get left edge rect + * + * @return line + */ + public Rect getEdgeLeft() + { + return new Rect(getLeftBottom(), getLeftTop()); + } + + + /** + * Get right edge rect + * + * @return line + */ + public Rect getEdgeRight() + { + return new Rect(getRightBottom(), getRightTop()); + } + + + /** + * Get top edge rect + * + * @return line + */ + public Rect getEdgeTop() + { + return new Rect(getLeftTop(), getRightTop()); + } + + + /** + * Get left bottom + * + * @return center + */ + public Coord getLeftBottom() + { + return new Coord(min.x, min.y); + } + + + /** + * Get left top + * + * @return center + */ + public Coord getLeftTop() + { + return new Coord(min.x, max.y); + } + + + /** + * @return highjest coordinates xy + */ + public Coord getMax() + { + return max; + } + + + /** + * @return lowest coordinates xy + */ + public Coord getMin() + { + return min; + } + + + /** + * Get right bottom + * + * @return center + */ + public Coord getRightBottom() + { + return new Coord(max.x, min.y); + } + + + /** + * Get right top + * + * @return center + */ + public Coord getRightTop() + { + return new Coord(max.x, max.y); + } + + + /** + * Get size (width, height) as (x,y) + * + * @return coord of width,height + */ + public Coord getSize() + { + return new Coord(Math.abs(min.x - max.x), Math.abs(min.y - max.y)); + } + + + /** + * Grow to sides in copy + * + * @param grow grow size (added to each side) + * @return grown copy + */ + public Rect grow(Coord grow) + { + return copy().grow_ip(grow); + } + + + /** + * Grow to sides in copy + * + * @param x x to add + * @param y y to add + * @return grown copy + */ + public Rect grow(double x, double y) + { + return copy().grow_ip(x, y); + } + + + /** + * Grow to sides in place + * + * @param grow grow size (added to each side) + * @return this + */ + public Rect grow_ip(Coord grow) + { + min.sub_ip(grow); + max.add_ip(grow); + return this; + } + + + /** + * Grow to sides in place + * + * @param x x to add + * @param y y to add + * @return this + */ + public Rect grow_ip(double x, double y) + { + min.sub_ip(x, y); + max.add_ip(x, y); + return this; + } + + + /** + * Grow down in copy + * + * @param down added pixels + * @return grown copy + */ + public Rect growDown(double down) + { + return copy().growDown_ip(down); + } + + + /** + * Grow down in place + * + * @param down added pixels + * @return this + */ + public Rect growDown_ip(double down) + { + min.sub_ip(0, down); + return this; + } + + + /** + * Grow to left in copy + * + * @param left added pixels + * @return grown copy + */ + public Rect growLeft(double left) + { + return copy().growLeft_ip(left); + } + + + /** + * Grow to left in place + * + * @param left added pixels + * @return this + */ + public Rect growLeft_ip(double left) + { + min.sub_ip(left, 0); + return this; + } + + + /** + * Grow to right in copy + * + * @param right added pixels + * @return grown copy + */ + public Rect growRight(double right) + { + return copy().growRight_ip(right); + } + + + /** + * Grow to right in place + * + * @param right added pixels + * @return this + */ + public Rect growRight_ip(double right) + { + max.add_ip(right, 0); + return this; + } + + + /** + * Grow up in copy + * + * @param add added pixels + * @return grown copy + */ + public Rect growUp(double add) + { + return copy().growUp_ip(add); + } + + + /** + * Grow up in place + * + * @param add added pixels + * @return this + */ + public Rect growUp_ip(double add) + { + max.add_ip(0, add); + return this; + } + + + /** + * Check if point is inside this rectangle + * + * @param point point to test + * @return is inside + */ + public boolean isInside(Coord point) + { + return Calc.inRange(point.x, min.x, max.x) && Calc.inRange(point.y, min.y, max.y); + } + + + /** + * Multiply in copy + * + * @param factor multiplier + * @return offset copy + */ + public Rect mul(double factor) + { + return copy().mul_ip(factor); + } + + + /** + * Multiply by number (useful for centered rects) + * + * @param x x multiplier + * @param y y multiplier + * @return copy multiplied + */ + public Rect mul(double x, double y) + { + return copy().mul_ip(x, y); + } + + + /** + * Multiply coord in place + * + * @param factor multiplier + * @return this + */ + public Rect mul_ip(double factor) + { + min.mul_ip(factor); + max.mul_ip(factor); + return this; + } + + + /** + * Multiply coord in place + * + * @param x multiplier x + * @param y multiplier y + * @return this + */ + public Rect mul_ip(double x, double y) + { + min.mul_ip(x, y, 1); + max.mul_ip(x, y, 1); + return this; + } + + + /** + * Round coords in copy + * + * @return copy, rounded + */ + public Rect round() + { + return new Rect(min.round(), max.round()); + } + + + /** + * Round this in place + * + * @return this + */ + public Rect round_ip() + { + min.round_ip(); + max.round_ip(); + return this; + } + + + /** + * Set to [0,0,coord.x,coord.y] + * + * @param coord size coord + */ + public void setTo(Coord coord) + { + setTo(0, 0, coord.x, coord.y); + } + + + /** + * Set to coordinates + * + * @param x1 lower x + * @param y1 lower y + * @param x2 upper x + * @param y2 upper y + */ + public void setTo(double x1, double y1, double x2, double y2) + { + min.x = Calc.min(x1, x2); + min.y = Calc.min(y1, y2); + max.x = Calc.max(x1, x2); + max.y = Calc.max(y1, y2); + } + + + /** + * Set to other rect's coordinates + * + * @param r other rect + */ + public void setTo(Rect r) + { + min.setTo(r.min); + max.setTo(r.max); + } + + + /** + * Subtract X and Y from all coordinates in a copy + * + * @param x x to subtract + * @param y y to subtract + * @return copy changed + */ + public Rect sub(double x, double y) + { + return sub(new Vec(x, y)); + } + + + /** + * Get offset copy (subtract) + * + * @param move offset vector + * @return offset copy + */ + public Rect sub(Vec move) + { + return copy().sub_ip(move); + } + + + /** + * Subtract X and Y from all coordinates in place + * + * @param x x to subtract + * @param y y to subtract + * @return this + */ + public Rect sub_ip(double x, double y) + { + return sub_ip(new Vec(x, y)); + } + + + /** + * Offset in place (subtract) + * + * @param move offset vector + * @return this + */ + public Rect sub_ip(Vec move) + { + min.sub_ip(move); + max.sub_ip(move); + return this; + } + + + @Override + public String toString() + { + return "rect{ " + min + " - " + max + " }"; + } + + + /** + * @return lower x + */ + public double x1() + { + return min.x; + } + + + /** + * @return upper x + */ + public double x2() + { + return max.x; + } + + + /** + * @return lower y + */ + public double y1() + { + return min.y; + } + + + /** + * @return upper y + */ + public double y2() + { + return max.y; + } +} diff --git a/src/com/porcupine/coord/Vec.java b/src/com/porcupine/coord/Vec.java new file mode 100644 index 0000000..bdde31c --- /dev/null +++ b/src/com/porcupine/coord/Vec.java @@ -0,0 +1,326 @@ +package com.porcupine.coord; + + +/** + * Vector in 2D/3D space. + * + * @author MightyPork + */ +public class Vec extends Coord { + + /** Vec [1;1;1] */ + @SuppressWarnings("hiding") + public static final Vec ONE = new Vec(1, 1, 1); + /** Zero vector */ + @SuppressWarnings("hiding") + public static final Vec ZERO = new Vec(0, 0, 0); + + + /** + * Get cross product of two vectors + * + * @param a 1st vector + * @param b 2nd vector + * @return cross product + */ + public static Vec cross(Vec a, Vec b) + { + return a.cross(b); + } + + + /** + * Get dot product of two vectors + * + * @param a 1st vector + * @param b 2nd vector + * @return dot product + */ + public static double dot(Vec a, Vec b) + { + return a.dot(b); + } + + + /** + * Generate random coord (gaussian) + * + * @param max max distance from 0 + * @return new coord + */ + public static Vec random(double max) + { + return new Vec(Coord.random(max)); + } + + + /** + * Generate random coord (min-max) + * + * @param max max distance from 0 + * @return new coord + */ + public static Vec random(double min, double max) + { + return new Vec(Coord.random(min, max)); + } + + + /** + * Scale vector + * + * @param a vector + * @param scale + * @return scaled copy + */ + public static Vec scale(Vec a, double scale) + { + return a.scale(scale); + } + + + /** + * Get vector size + * + * @param vec vector to get size of + * @return size in units + */ + public static double size(Vec vec) + { + return vec.size(); + } + + + /** + * Create zero vector + */ + public Vec() { + super(); + } + + + /** + * Create vector as a copy of another + * + * @param copied copied vector + */ + public Vec(Coord copied) { + super(copied); + } + + + /** + * Create 2D vector + * + * @param x x coordinate + * @param y y coordinate + */ + public Vec(Number x, Number y) { + super(x, y); + } + + + /** + * Create 3D vector + * + * @param x x coordinate + * @param y y coordinate + * @param z z coordinate + */ + public Vec(Number x, Number y, Number z) { + super(x, y, z); + } + + + @Override + public Vec copy() + { + return new Vec(this); + } + + + /** + * Multiply by other vector, vector multiplication + * + * @param vec other vector + * @return copy multiplied + */ + public Vec cross(Vec vec) + { + return copy().cross_ip(vec); + } + + + /** + * Multiply by other vector, vector multiplication; in place + * + * @param vec other vector + * @return this + */ + public Vec cross_ip(Vec vec) + { + setTo(y * vec.z - z * vec.y, z * vec.x - x * vec.z, x * vec.y - y * vec.x); + return this; + } + + + /** + * Get dot product + * + * @param vec other vector + * @return dot product + */ + public double dot(Vec vec) + { + return x * vec.x + y * vec.y + z * vec.z; + } + + + // STATIC + + /** + * Negate all coordinates (* -1) + * + * @return negated coordinate + */ + public Vec neg() + { + return copy().neg_ip(); + } + + + /** + * Negate all coordinates (* -1), in place + * + * @return this + */ + public Vec neg_ip() + { + scale_ip(-1); + return this; + } + + + /** + * Scale vector to given size + * + * @param size size we need + * @return scaled vector + */ + public Vec norm(double size) + { + return copy().norm_ip(size); + } + + + /** + * Scale vector to given size, in place + * + * @param size size we need + * @return scaled vector + */ + public Vec norm_ip(double size) + { + if (size() == 0) { + z = -1; + } + if (size == 0) { + setTo(0, 0, 0); + return this; + } + double k = size / size(); + scale_ip(k); + return this; + } + + + /** + * offset randomly + * + * @param max max +- offset + * @return offset coord + */ + @Override + public Vec random_offset(double max) + { + return (Vec) super.random_offset(max); + } + + + /** + * offset randomly + * + * @param min min offset + * @param max max offset + * @return offset coord + */ + @Override + public Vec random_offset(double min, double max) + { + return (Vec) super.random_offset(min, max); + } + + + /** + * offset randomly in place + * + * @param max max +- offset + * @return this + */ + @Override + public Vec random_offset_ip(double max) + { + return (Vec) super.random_offset_ip(max); + } + + + /** + * offset randomly in place + * + * @param min min offset + * @param max max offset + * @return this + */ + @Override + public Vec random_offset_ip(double min, double max) + { + return (Vec) super.random_offset_ip(min, max); + } + + + /** + * Multiply all coordinates by factor; scalar multiplication + * + * @param factor multiplier + * @return copy multiplied + */ + public Vec scale(double factor) + { + return copy().scale_ip(factor); + } + + + /** + * Multiply all coordinates by factor, in place + * + * @param factor multiplier + * @return this + */ + public Vec scale_ip(double factor) + { + return (Vec) mul_ip(factor); + } + + + /** + * Get vector size + * + * @return vector size in units + */ + @Override + public double size() + { + return Math.sqrt(x * x + y * y + z * z); + } + +} diff --git a/src/com/porcupine/ion/AbstractIonList.java b/src/com/porcupine/ion/AbstractIonList.java new file mode 100644 index 0000000..d96a4a9 --- /dev/null +++ b/src/com/porcupine/ion/AbstractIonList.java @@ -0,0 +1,71 @@ +package com.porcupine.ion; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + + +/** + * Ionizable Arraylist + * + * @author MightyPork + * @param + */ +public abstract class AbstractIonList extends ArrayList implements Ionizable { + + @Override + public void ionRead(InputStream in) throws IOException + { + while (true) { + byte b = StreamUtils.readByte(in); + + if (b == IonMarks.ENTRY) { + T value = (T) Ion.readObject(in); + add(value); + } else if (b == IonMarks.END) { + break; + } else { + throw new RuntimeException("Unexpected mark in AbstractIonList: " + Integer.toHexString(b)); + } + } + ionReadCustomData(in); + } + + + @Override + public void ionWrite(OutputStream out) throws IOException + { + for (T entry : this) { + if (entry instanceof IonizableOptional && !((IonizableOptional) entry).ionShouldSave()) continue; + StreamUtils.writeByte(out, IonMarks.ENTRY); + Ion.writeObject(out, entry); + } + StreamUtils.writeByte(out, IonMarks.END); + ionWriteCustomData(out); + } + + + /** + * Read custom data of this AbstractIonList implementation + * + * @param in input stream + */ + public void ionReadCustomData(InputStream in) + {} + + + /** + * Write custom data of this AbstractIonList implementation + * + * @param out output stream + */ + public void ionWriteCustomData(OutputStream out) + {} + + + @Override + public abstract byte ionMark(); + +} diff --git a/src/com/porcupine/ion/AbstractIonMap.java b/src/com/porcupine/ion/AbstractIonMap.java new file mode 100644 index 0000000..f724399 --- /dev/null +++ b/src/com/porcupine/ion/AbstractIonMap.java @@ -0,0 +1,88 @@ +package com.porcupine.ion; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.LinkedHashMap; + + +/** + * Ionizable HashMap + * + * @author MightyPork + * @param + */ +public abstract class AbstractIonMap extends LinkedHashMap implements Ionizable { + + @Override + public V get(Object key) + { + return super.get(key); + } + + + @Override + public V put(String key, V value) + { + return super.put(key, value); + } + + + @Override + public void ionRead(InputStream in) throws IOException + { + while (true) { + byte b = StreamUtils.readByte(in); + if (b == IonMarks.ENTRY) { + String key = StreamUtils.readStringBytes(in); + V value = (V) Ion.readObject(in); + put(key, value); + } else if (b == IonMarks.END) { + break; + } else { + throw new RuntimeException("Unexpected mark in IonMap: " + Integer.toHexString(b)); + } + } + ionReadCustomData(in); + } + + + @Override + public void ionWrite(OutputStream out) throws IOException + { + for (java.util.Map.Entry entry : entrySet()) { + StreamUtils.writeByte(out, IonMarks.ENTRY); + StreamUtils.writeStringBytes(out, entry.getKey()); + Ion.writeObject(out, entry.getValue()); + } + StreamUtils.writeByte(out, IonMarks.END); + ionWriteCustomData(out); + } + + + /** + * Read custom data of this AbstractIonMap implementation + * + * @param in input stream + */ + public void ionReadCustomData(InputStream in) + {} + + + /** + * Write custom data of this AbstractIonMap implementation + * + * @param out output stream + */ + public void ionWriteCustomData(OutputStream out) + {} + + + @Override + public byte ionMark() + { + return IonMarks.MAP; + } + +} diff --git a/src/com/porcupine/ion/Ion.java b/src/com/porcupine/ion/Ion.java new file mode 100644 index 0000000..84419a2 --- /dev/null +++ b/src/com/porcupine/ion/Ion.java @@ -0,0 +1,273 @@ +package com.porcupine.ion; + + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +import com.porcupine.math.Calc; + + +/** + * Universal data storage system + * + * @author MightyPork + */ +public class Ion { + + /** Ionizables */ + private static Map> customIonizables = new HashMap>(); + + // register default ionizables + static { + registerIonizable(IonMarks.MAP, IonMap.class); + registerIonizable(IonMarks.LIST, IonList.class); + } + + + /** + * Register new Ionizable for direct reconstructing. + * + * @param mark byte mark to be used, see {@link IonMarks} for reference. + * @param objClass class of the registered Ionizable + */ + public static void registerIonizable(byte mark, Class objClass) + { + if (customIonizables.containsKey(mark)) { + throw new RuntimeException("IonMark " + mark + " is already used @ " + objClass.getSimpleName()); + } + customIonizables.put(mark, objClass); + } + + + /** + * Load Ion object from file. + * + * @param file file path + * @return the loaded object + */ + public static Object fromFile(String file) + { + return fromFile(new File(file)); + } + + + /** + * Load Ion object from file. + * + * @param file file + * @return the loaded object + */ + public static Object fromFile(File file) + { + try { + InputStream in = new FileInputStream(file); + + Object obj = fromStream(in); + + in.close(); + return obj; + } catch (FileNotFoundException e) { + System.err.println("Could not find ION file " + file); + return null; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + /** + * Load Ion object from stream. + * + * @param in input stream + * @return the loaded object + */ + public static Object fromStream(InputStream in) + { + try { + return readObject(in); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + /** + * Store Ion object to file. + * + * @param path file path + * @param obj object to store + */ + public static void toFile(String path, Object obj) + { + toFile(new File(path), obj); + } + + + /** + * Store Ion object to file. + * + * @param path file path + * @param obj object to store + */ + public static void toFile(File path, Object obj) + { + try { + String f = path.toString(); + File dir = new File(f.substring(0, f.lastIndexOf(File.separator))); + + dir.mkdirs(); + + OutputStream out = new FileOutputStream(path); + + toStream(out, obj); + + out.flush(); + out.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + /** + * Store Ion object to output stream. + * + * @param out output stream * + * @param obj object to store + */ + public static void toStream(OutputStream out, Object obj) + { + try { + writeObject(out, obj); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + /** + * Read single ionizable or primitive object from input stream + * + * @param in input stream + * @return the loaded object + */ + public static Object readObject(InputStream in) + { + try { + int bi = in.read(); + if (bi == -1) throw new RuntimeException("Unexpected end of stream."); + byte b = (byte) bi; + if (customIonizables.containsKey(b)) { + Ionizable ion = ((Ionizable) customIonizables.get(b).newInstance()); + ion.ionRead(in); + return ion; + } + + switch (b) { + case IonMarks.BOOLEAN: + return StreamUtils.readBoolean(in); + case IonMarks.BYTE: + return StreamUtils.readByte(in); + case IonMarks.CHAR: + return StreamUtils.readChar(in); + case IonMarks.SHORT: + return StreamUtils.readShort(in); + case IonMarks.INT: + return StreamUtils.readInt(in); + case IonMarks.LONG: + return StreamUtils.readLong(in); + case IonMarks.FLOAT: + return StreamUtils.readFloat(in); + case IonMarks.DOUBLE: + return StreamUtils.readDouble(in); + case IonMarks.STRING: + String s = StreamUtils.readString(in); + return s; + default: + throw new RuntimeException("Invalid Ion mark " + Integer.toHexString(bi)); + } + + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + + /** + * Write single ionizable or primitive object to output stream + * + * @param out output stream + * @param obj stored object + */ + public static void writeObject(OutputStream out, Object obj) + { + try { + if (obj instanceof Ionizable) { + out.write(((Ionizable) obj).ionMark()); + ((Ionizable) obj).ionWrite(out); + return; + } + + if (obj instanceof Boolean) { + out.write(IonMarks.BOOLEAN); + StreamUtils.writeBoolean(out, (Boolean) obj); + return; + } + + if (obj instanceof Byte) { + out.write(IonMarks.BYTE); + StreamUtils.writeByte(out, (Byte) obj); + return; + } + + if (obj instanceof Character) { + out.write(IonMarks.CHAR); + StreamUtils.writeChar(out, (Character) obj); + return; + } + + if (obj instanceof Short) { + out.write(IonMarks.SHORT); + StreamUtils.writeShort(out, (Short) obj); + return; + } + + if (obj instanceof Integer) { + out.write(IonMarks.INT); + StreamUtils.writeInt(out, (Integer) obj); + return; + } + + if (obj instanceof Long) { + out.write(IonMarks.LONG); + StreamUtils.writeLong(out, (Long) obj); + return; + } + + if (obj instanceof Float) { + out.write(IonMarks.FLOAT); + StreamUtils.writeFloat(out, (Float) obj); + return; + } + + if (obj instanceof Double) { + out.write(IonMarks.DOUBLE); + StreamUtils.writeDouble(out, (Double) obj); + return; + } + + if (obj instanceof String) { + out.write(IonMarks.STRING); + StreamUtils.writeString(out, (String) obj); + return; + } + + throw new RuntimeException(Calc.cname(obj) + " can't be stored to Ion storage."); + + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + +} diff --git a/src/com/porcupine/ion/IonList.java b/src/com/porcupine/ion/IonList.java new file mode 100644 index 0000000..f013505 --- /dev/null +++ b/src/com/porcupine/ion/IonList.java @@ -0,0 +1,197 @@ +package com.porcupine.ion; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + + +/** + * Ionizable Arraylist + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class IonList extends ArrayList implements Ionizable { + + public boolean getBoolean(int index) + { + return (Boolean) get(index); + } + + + public boolean getBool(int index) + { + return (Boolean) get(index); + } + + + public byte getByte(int index) + { + return (Byte) get(index); + } + + + public char getChar(int index) + { + return (Character) get(index); + } + + + public char getCharacter(int index) + { + return (Character) get(index); + } + + + public short getShort(int index) + { + return (Short) get(index); + } + + + public int getInteger(int index) + { + return (Integer) get(index); + } + + + public int getInt(int index) + { + return (Integer) get(index); + } + + + public long getLong(int index) + { + return (Long) get(index); + } + + + public float getFloat(int index) + { + return (Float) get(index); + } + + + public double getDouble(int index) + { + return (Double) get(index); + } + + + public String getString(int index) + { + return (String) get(index); + } + + + @Override + public Object get(int index) + { + return super.get(index); + } + + + public void addBoolean(boolean num) + { + add(num); + } + + + public void addBool(boolean num) + { + add(num); + } + + + public void addByte(int num) + { + add((byte) num); + } + + + public void addChar(char num) + { + add(num); + } + + + public void addShort(int num) + { + add((short) num); + } + + + public void addInteger(int num) + { + add(num); + } + + + public void addInt(int num) + { + add(num); + } + + + public void addLong(long num) + { + add(num); + } + + + public void addFloat(double num) + { + add((float) num); + } + + + public void addDouble(double num) + { + add(num); + } + + + public void addString(String num) + { + add(num); + } + + + @Override + public void ionRead(InputStream in) throws IOException + { + while (true) { + byte b = StreamUtils.readByte(in); + if (b == IonMarks.ENTRY) { + Object value = Ion.readObject(in); + add(value); + } else if (b == IonMarks.END) { + break; + } else { + throw new RuntimeException("Unexpected mark in IonList: " + Integer.toHexString(b)); + } + } + } + + + @Override + public void ionWrite(OutputStream out) throws IOException + { + for (Object entry : this) { + StreamUtils.writeByte(out, IonMarks.ENTRY); + Ion.writeObject(out, entry); + } + StreamUtils.writeByte(out, IonMarks.END); + } + + + @Override + public byte ionMark() + { + return IonMarks.LIST; + } + +} diff --git a/src/com/porcupine/ion/IonMap.java b/src/com/porcupine/ion/IonMap.java new file mode 100644 index 0000000..53005f4 --- /dev/null +++ b/src/com/porcupine/ion/IonMap.java @@ -0,0 +1,194 @@ +package com.porcupine.ion; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.LinkedHashMap; +import java.util.Map.Entry; + + +/** + * Ionizable HashMap + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class IonMap extends LinkedHashMap implements Ionizable { + + public boolean getBoolean(String key) + { + return (Boolean) get(key); + } + + + public boolean getBool(String key) + { + return (Boolean) get(key); + } + + + public byte getByte(String key) + { + return (Byte) get(key); + } + + + public char getChar(String key) + { + return (Character) get(key); + } + + + public short getShort(String key) + { + return (Short) get(key); + } + + + public int getInt(String key) + { + return (Integer) get(key); + } + + + public long getLong(String key) + { + return (Long) get(key); + } + + + public float getFloat(String key) + { + return (Float) get(key); + } + + + public double getDouble(String key) + { + return (Double) get(key); + } + + + public String getString(String key) + { + return (String) get(key); + } + + + @Override + public Object get(Object arg0) + { + return super.get(arg0); + } + + + public void putBoolean(String key, boolean num) + { + put(key, num); + } + + + public void putBool(String key, boolean num) + { + put(key, num); + } + + + public void putByte(String key, int num) + { + put(key, (byte) num); + } + + + public void putChar(String key, char num) + { + put(key, num); + } + + + public void putCharacter(String key, char num) + { + put(key, num); + } + + + public void putShort(String key, int num) + { + put(key, num); + } + + + public void putInt(String key, int num) + { + put(key, num); + } + + + public void putInteger(String key, int num) + { + put(key, num); + } + + + public void putLong(String key, long num) + { + put(key, num); + } + + + public void putFloat(String key, double num) + { + put(key, (float) num); + } + + + public void putDouble(String key, double num) + { + put(key, num); + } + + + public void putString(String key, String num) + { + put(key, num); + } + + + @Override + public void ionRead(InputStream in) throws IOException + { + while (true) { + byte b = StreamUtils.readByte(in); + if (b == IonMarks.ENTRY) { + String key = StreamUtils.readStringBytes(in); + Object value = Ion.readObject(in); + put(key, value); + } else if (b == IonMarks.END) { + break; + } else { + throw new RuntimeException("Unexpected mark in IonMap: " + Integer.toHexString(b)); + } + } + } + + + @Override + public void ionWrite(OutputStream out) throws IOException + { + for (Entry entry : entrySet()) { + StreamUtils.writeByte(out, IonMarks.ENTRY); + StreamUtils.writeStringBytes(out, entry.getKey()); + Ion.writeObject(out, entry.getValue()); + } + StreamUtils.writeByte(out, IonMarks.END); + } + + + @Override + public byte ionMark() + { + return IonMarks.MAP; + } + +} diff --git a/src/com/porcupine/ion/IonMarks.java b/src/com/porcupine/ion/IonMarks.java new file mode 100644 index 0000000..1fb018f --- /dev/null +++ b/src/com/porcupine/ion/IonMarks.java @@ -0,0 +1,57 @@ +package com.porcupine.ion; + + +/** + * Byte marks used to structure data in Ion files. + * + * @author MightyPork + */ +public class IonMarks { + + /** Null value */ + public static final byte NULL = 0; + + /** Boolean value */ + public static final byte BOOLEAN = 1; + + /** Byte value */ + public static final byte BYTE = 2; + + /** Character value */ + public static final byte CHAR = 3; + + /** Short value */ + public static final byte SHORT = 4; + + /** Integer value */ + public static final byte INT = 5; + + /** Long value */ + public static final byte LONG = 6; + + /** Float value */ + public static final byte FLOAT = 7; + + /** Double value */ + public static final byte DOUBLE = 8; + + /** String value */ + public static final byte STRING = 9; + + /** List value (begin) - contains entries, ends with END */ + public static final byte LIST = 10; + + /** Map value (begin) - contains entries, ends with END */ + public static final byte MAP = 11; + + /** + * List / Map entry
+ * In list directly followed by entry value. In map followed by (string) key + * and the entry value. + */ + public static final byte ENTRY = 12; + + /** End of List / Map */ + public static final byte END = 13; + +} diff --git a/src/com/porcupine/ion/Ionizable.java b/src/com/porcupine/ion/Ionizable.java new file mode 100644 index 0000000..3164fff --- /dev/null +++ b/src/com/porcupine/ion/Ionizable.java @@ -0,0 +1,44 @@ +package com.porcupine.ion; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + + +/** + * Object that can be saved to and loaded from Ion file.
+ * All classes implementing Ionizable must be registered to {@link Ion} using + * Ion.registerIonizable(obj.class). + * + * @author MightyPork + */ +public interface Ionizable { + + /** + * Load data from the input stream. Mark has already been read, begin + * reading right after it. + * + * @param in input stream + * @throws IOException at IO error + */ + public void ionRead(InputStream in) throws IOException; + + + /** + * Store data to output stream. mark has already been written, begin right + * after it. + * + * @param out Output stream + * @throws IOException at IO error + */ + public void ionWrite(OutputStream out) throws IOException; + + + /** + * Get Ion mark byte. + * + * @return Ion mark byte. + */ + public byte ionMark(); +} diff --git a/src/com/porcupine/ion/IonizableOptional.java b/src/com/porcupine/ion/IonizableOptional.java new file mode 100644 index 0000000..0cc990c --- /dev/null +++ b/src/com/porcupine/ion/IonizableOptional.java @@ -0,0 +1,17 @@ +package com.porcupine.ion; + + +/** + * Optional ionizable + * + * @author MightyPork + */ +public interface IonizableOptional extends Ionizable { + + /** + * Get if this ionizable should be saved to a list + * + * @return should save + */ + public boolean ionShouldSave(); +} diff --git a/src/com/porcupine/ion/StreamUtils.java b/src/com/porcupine/ion/StreamUtils.java new file mode 100644 index 0000000..477628d --- /dev/null +++ b/src/com/porcupine/ion/StreamUtils.java @@ -0,0 +1,267 @@ +package com.porcupine.ion; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + + +/** + * Utilities to store and load objects to streams. + * + * @author MightyPork + */ + +@SuppressWarnings("javadoc") +public class StreamUtils { + + private static ByteBuffer bi = ByteBuffer.allocate(Integer.SIZE / 8); + private static ByteBuffer bd = ByteBuffer.allocate(Double.SIZE / 8); + private static ByteBuffer bf = ByteBuffer.allocate(Float.SIZE / 8); + private static ByteBuffer bc = ByteBuffer.allocate(Character.SIZE / 8); + private static ByteBuffer bl = ByteBuffer.allocate(Long.SIZE / 8); + private static ByteBuffer bs = ByteBuffer.allocate(Short.SIZE / 8); + + private static byte[] ai = new byte[Integer.SIZE / 8]; + private static byte[] ad = new byte[Double.SIZE / 8]; + private static byte[] af = new byte[Float.SIZE / 8]; + private static byte[] ac = new byte[Character.SIZE / 8]; + private static byte[] al = new byte[Long.SIZE / 8]; + private static byte[] as = new byte[Short.SIZE / 8]; + + + // CONVERSIONS + + private static byte[] convBool(boolean bool) + { + return new byte[] { (byte) (bool ? 1 : 0) }; + } + + + private static byte[] convByte(byte num) + { + return new byte[] { num }; + } + + + private static byte[] convChar(char num) + { + bc.clear(); + bc.putChar(num); + return bc.array(); + } + + + private static byte[] convShort(short num) + { + bs.clear(); + bs.putShort(num); + return bs.array(); + } + + + private static byte[] convInt(int num) + { + bi.clear(); + bi.putInt(num); + return bi.array(); + } + + + private static byte[] convLong(long num) + { + bl.clear(); + bl.putLong(num); + return bl.array(); + } + + + private static byte[] convFloat(float num) + { + bf.clear(); + bf.putFloat(num); + return bf.array(); + } + + + private static byte[] convDouble(double num) + { + bd.clear(); + bd.putDouble(num); + return bd.array(); + } + + + private static byte[] convString(String str) + { + char[] chars = str.toCharArray(); + + ByteBuffer bstr = ByteBuffer.allocate((Character.SIZE / 8) * chars.length + (Character.SIZE / 8)); + for (char c : chars) { + bstr.putChar(c); + } + + bstr.putChar((char) 0); + + return bstr.array(); + } + + + private static byte[] convString_b(String str) + { + char[] chars = str.toCharArray(); + ByteBuffer bstr = ByteBuffer.allocate((Byte.SIZE / 8) * chars.length + 1); + for (char c : chars) { + bstr.put((byte) c); + } + bstr.put((byte) 0); + + return bstr.array(); + } + + + public static void writeBoolean(OutputStream out, boolean num) throws IOException + { + out.write(convBool(num)); + } + + + public static void writeByte(OutputStream out, byte num) throws IOException + { + out.write(convByte(num)); + } + + + public static void writeChar(OutputStream out, char num) throws IOException + { + out.write(convChar(num)); + } + + + public static void writeShort(OutputStream out, short num) throws IOException + { + out.write(convShort(num)); + } + + + public static void writeInt(OutputStream out, int num) throws IOException + { + out.write(convInt(num)); + } + + + public static void writeLong(OutputStream out, long num) throws IOException + { + out.write(convLong(num)); + } + + + public static void writeFloat(OutputStream out, float num) throws IOException + { + out.write(convFloat(num)); + } + + + public static void writeDouble(OutputStream out, double num) throws IOException + { + out.write(convDouble(num)); + } + + + public static void writeString(OutputStream out, String str) throws IOException + { + out.write(convString(str)); + } + + + public static void writeStringBytes(OutputStream out, String str) throws IOException + { + out.write(convString_b(str)); + } + + + // READING + + public static boolean readBoolean(InputStream in) throws IOException + { + return in.read() > 0; + } + + + public static byte readByte(InputStream in) throws IOException + { + return (byte) in.read(); + } + + + public static char readChar(InputStream in) throws IOException + { + in.read(ac, 0, ac.length); + ByteBuffer buf = ByteBuffer.wrap(ac); + return buf.getChar(); + } + + + public static short readShort(InputStream in) throws IOException + { + in.read(as, 0, as.length); + ByteBuffer buf = ByteBuffer.wrap(as); + return buf.getShort(); + } + + + public static long readLong(InputStream in) throws IOException + { + in.read(al, 0, al.length); + ByteBuffer buf = ByteBuffer.wrap(al); + return buf.getLong(); + } + + + public static int readInt(InputStream in) throws IOException + { + in.read(ai, 0, ai.length); + ByteBuffer buf = ByteBuffer.wrap(ai); + return buf.getInt(); + } + + + public static float readFloat(InputStream in) throws IOException + { + in.read(af, 0, af.length); + ByteBuffer buf = ByteBuffer.wrap(af); + return buf.getFloat(); + } + + + public static double readDouble(InputStream in) throws IOException + { + in.read(ad, 0, ad.length); + ByteBuffer buf = ByteBuffer.wrap(ad); + return buf.getDouble(); + } + + + public static String readString(InputStream in) throws IOException + { + String s = ""; + char c; + while ((c = readChar(in)) > 0) { + s += c; + } + return s; + } + + + public static String readStringBytes(InputStream in) throws IOException + { + String s = ""; + byte b; + while ((b = readByte(in)) > 0) { + s += (char) b; + } + return s; + } + +} diff --git a/src/com/porcupine/math/Calc.java b/src/com/porcupine/math/Calc.java new file mode 100644 index 0000000..7695f64 --- /dev/null +++ b/src/com/porcupine/math/Calc.java @@ -0,0 +1,931 @@ +package com.porcupine.math; + + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.lwjgl.BufferUtils; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Vec; + + +/** + * Math helper + * + * @author MightyPork + */ +public class Calc { + + /** Square root of two */ + public static final double SQ2 = 1.41421356237; + + + /** + * Get distance from 2D line to 2D point [X,Y] + * + * @param lineDirVec line directional vector + * @param linePoint point of line + * @param point point coordinate + * @return distance + */ + public static double linePointDist(Vec lineDirVec, Coord linePoint, Coord point) + { + // line point L[lx,ly] + double lx = linePoint.x; + double ly = linePoint.y; + + // line equation ax+by+c=0 + double a = -lineDirVec.y; + double b = lineDirVec.x; + double c = -a * lx - b * ly; + + // checked point P[x,y] + double x = point.x; + double y = point.y; + + // distance + return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b); + } + + + /** + * Get distance from 2D line to 2D point [X,Z] + * + * @param lineDirVec line directional vector + * @param linePoint point of line + * @param point point coordinate + * @return distance + */ + public static double linePointDistXZ(Vec lineDirVec, Coord linePoint, Coord point) + { + return linePointDist(new Vec(lineDirVec.x, lineDirVec.z), new Coord(linePoint.x, linePoint.z), new Coord(point.x, point.z)); + } + + + /** + * Get longest side of a right-angled triangle + * + * @param a side a (opposite) + * @param b side b (adjacent) + * @return longest side (hypotenuse) + */ + public static double pythC(double a, double b) + { + return Math.sqrt(square(a) + square(b)); + } + + + /** + * Get adjacent side of a right-angled triangle + * + * @param a side a (opposite) + * @param c side c (hypotenuse) + * @return side b (adjacent) + */ + public static double pythB(double a, double c) + { + return Math.sqrt(square(c) - square(a)); + } + + + /** + * Get opposite side of a right-angled triangle + * + * @param b side b (adjacent) + * @param c side c (hypotenuse) + * @return side a (opposite) + */ + public static double pythA(double b, double c) + { + return Math.sqrt(square(c) - square(b)); + } + + private static class Angles { + + public static double delta(double alpha, double beta, double a360) + { + while (Math.abs(alpha - beta) > a360 / 2D) { + alpha = norm(alpha + a360 / 2D, a360); + beta = norm(beta + a360 / 2D, a360); + } + + return beta - alpha; + } + + + public static double norm(double angle, double a360) + { + while (angle < 0) + angle += a360; + while (angle > a360) + angle -= a360; + return angle; + } + } + + /** + * Calc subclass with buffer utils. + * + * @author MightyPork + */ + public static class Buffers { + + /** + * Create java.nio.FloatBuffer of given floats, and flip it. + * + * @param obj floats or float array + * @return float buffer + */ + public static FloatBuffer mkFillBuff(float... obj) + { + return (FloatBuffer) BufferUtils.createFloatBuffer(obj.length).put(obj).flip(); + } + + + /** + * Fill java.nio.FloatBuffer with floats or float array + * + * @param buff + * @param obj + */ + public static void fill(FloatBuffer buff, float... obj) + { + buff.put(obj); + buff.flip(); + } + + + /** + * Create new java.nio.FloatBuffer of given length + * + * @param count elements + * @return the new java.nio.FloatBuffer + */ + public static FloatBuffer alloc(int count) + { + return BufferUtils.createFloatBuffer(count); + } + + } + + /** + * Angle calculations for degrees. + * + * @author MightyPork + */ + public static class Deg { + + /** 180° in degrees */ + public static final double a180 = 180; + /** 270° in degrees */ + public static final double a270 = 270; + /** 360° in degrees */ + public static final double a360 = 360; + /** 45° in degrees */ + public static final double a45 = 45; + /** 90° in degrees */ + public static final double a90 = 90; + + + /** + * Subtract two angles alpha - beta + * + * @param alpha first angle + * @param beta second angle + * @return (alpha - beta) in degrees + */ + public static double delta(double alpha, double beta) + { + return Angles.delta(alpha, beta, a360); + } + + + /** + * Difference of two angles (absolute value of delta) + * + * @param alpha first angle + * @param beta second angle + * @return difference in radians + */ + public static double diff(double alpha, double beta) + { + return Math.abs(Angles.delta(alpha, beta, a360)); + } + + + /** + * Cosinus in degrees + * + * @param deg angle in degrees + * @return cosinus + */ + public static double cos(double deg) + { + return Math.cos(toRad(deg)); + } + + + /** + * Sinus in degrees + * + * @param deg angle in degrees + * @return sinus + */ + public static double sin(double deg) + { + return Math.sin(toRad(deg)); + } + + + /** + * Tangents in degrees + * + * @param deg angle in degrees + * @return tangents + */ + public static double tan(double deg) + { + return Math.tan(toRad(deg)); + } + + + /** + * Angle normalized to 0-360 range + * + * @param angle angle to normalize + * @return normalized angle + */ + public static double norm(double angle) + { + return Angles.norm(angle, a360); + } + + + /** + * Convert to radians + * + * @param deg degrees + * @return radians + */ + public static double toRad(double deg) + { + return Math.toRadians(deg); + } + + + /** + * Round angle to 0,45,90,135... + * + * @param deg angle in deg. to round + * @param x rounding increment (45 - round to 0,45,90...) + * @return rounded + */ + public static int roundX(double deg, double x) + { + double half = x / 2d; + deg += half; + deg = norm(deg); + int times = (int) Math.floor(deg / x); + double a = times * x; + if (a == 360) a = 0; + return (int) Math.round(a); + } + + + /** + * Round angle to 0,45,90,135... + * + * @param deg angle in deg. to round + * @return rounded + */ + public static int round45(double deg) + { + return roundX(deg, 45); + } + + + /** + * Round angle to 0,90,180,270 + * + * @param deg angle in deg. to round + * @return rounded + */ + public static int round90(double deg) + { + return roundX(deg, 90); + } + + + /** + * Round angle to 0,15,30,45,60,75,90... + * + * @param deg angle in deg to round + * @return rounded + */ + public static int round15(double deg) + { + return roundX(deg, 15); + } + } + + /** + * Angle calculations for radians. + * + * @author MightyPork + */ + public static class Rad { + + /** 180° in radians */ + public static final double a180 = Math.PI; + /** 270° in radians */ + public static final double a270 = Math.PI * 1.5D; + /** 360° in radians */ + public static final double a360 = Math.PI * 2D; + /** 45° in radians */ + public static final double a45 = Math.PI / 4D; + /** 90° in radians */ + public static final double a90 = Math.PI / 2D; + + + /** + * Subtract two angles alpha - beta + * + * @param alpha first angle + * @param beta second angle + * @return (alpha - beta) in radians + */ + public static double delta(double alpha, double beta) + { + return Angles.delta(alpha, beta, a360); + } + + + /** + * Difference of two angles (absolute value of delta) + * + * @param alpha first angle + * @param beta second angle + * @return difference in radians + */ + public static double diff(double alpha, double beta) + { + return Math.abs(Angles.delta(alpha, beta, a360)); + } + + + /** + * Cos + * + * @param rad angle in rads + * @return cos + */ + public static double cos(double rad) + { + return Math.cos(rad); + } + + + /** + * Sin + * + * @param rad angle in rads + * @return sin + */ + public static double sin(double rad) + { + return Math.sin(rad); + } + + + /** + * Tan + * + * @param rad angle in rads + * @return tan + */ + public static double tan(double rad) + { + return Math.tan(rad); + } + + + /** + * Angle normalized to 0-2*PI range + * + * @param angle angle to normalize + * @return normalized angle + */ + public static double norm(double angle) + { + return Angles.norm(angle, a360); + } + + + /** + * Convert to degrees + * + * @param rad radians + * @return degrees + */ + public static double toDeg(double rad) + { + return Math.toDegrees(rad); + } + } + + private static Random rand = new Random(); + + + /** + * Get volume of a sphere + * + * @param radius sphere radius + * @return volume in cubic units + */ + public static double sphereGetVolume(double radius) + { + return (4D / 3D) * Math.PI * cube(radius); + } + + + /** + * Get radius of a sphere + * + * @param volume sphere volume + * @return radius in units + */ + public static double sphereGetRadius(double volume) + { + return Math.cbrt((3D * volume) / (4 * Math.PI)); + } + + + /** + * Get surface of a circle + * + * @param radius circle radius + * @return volume in square units + */ + public static double circleGetSurface(double radius) + { + return Math.PI * square(radius); + } + + + /** + * Get radius of a circle + * + * @param surface circle volume + * @return radius in units + */ + public static double circleGetRadius(double surface) + { + return Math.sqrt(surface / Math.PI); + } + + + /** + * Check if objects are equal (for equals function) + * + * @param a + * @param b + * @return are equal + */ + public static boolean areObjectsEqual(Object a, Object b) + { + return a == null ? b == null : a.equals(b); + } + + + /** + * Private clamping helper. + * + * @param number number to be clamped + * @param min min value + * @param max max value + * @return clamped double + */ + private static double clamp_double(Number number, Number min, Number max) + { + double n = number.doubleValue(); + double mind = min.doubleValue(); + double maxd = max.doubleValue(); + if (n > maxd) n = maxd; + if (n < mind) n = mind; + if (Double.isNaN(n)) return mind; + return n; + } + + + /** + * Private clamping helper. + * + * @param number number to be clamped + * @param min min value + * @return clamped double + */ + private static double clamp_double(Number number, Number min) + { + double n = number.doubleValue(); + double mind = min.doubleValue(); + if (n < mind) n = mind; + return n; + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * DOUBLE version + * + * @param number clamped number + * @param min minimal allowed value + * @param max maximal allowed value + * @return result + */ + public static double clampd(Number number, Number min, Number max) + { + return clamp_double(number, min, max); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * FLOAT version + * + * @param number clamped number + * @param min minimal allowed value + * @param max maximal allowed value + * @return result + */ + public static float clampf(Number number, Number min, Number max) + { + return (float) clamp_double(number, min, max); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * INTEGER version + * + * @param number clamped number + * @param min minimal allowed value + * @param max maximal allowed value + * @return result + */ + public static int clampi(Number number, Number min, Number max) + { + return (int) Math.round(clamp_double(number, min, max)); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * INTEGER version + * + * @param number clamped number + * @param range range + * @return result + */ + public static int clampi(Number number, Range range) + { + return (int) Math.round(clamp_double(number, range.getMin(), range.getMax())); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * DOUBLE version + * + * @param number clamped number + * @param range range + * @return result + */ + public static double clampd(Number number, Range range) + { + return clamp_double(number, range.getMin(), range.getMax()); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * FLOAT version + * + * @param number clamped number + * @param range range + * @return result + */ + public static float clampf(Number number, Range range) + { + return (float) clamp_double(number, range.getMin(), range.getMax()); + } + + + /** + * Clamp number to min and infinite bounds, inclusive.
+ * DOUBLE version + * + * @param number clamped number + * @param min minimal allowed value + * @return result + */ + public static double clampd(Number number, Number min) + { + return clamp_double(number, min); + } + + + /** + * Clamp number to min and infinite bounds, inclusive.
+ * FLOAT version + * + * @param number clamped number + * @param min minimal allowed value + * @return result + */ + public static float clampf(Number number, Number min) + { + return (float) clamp_double(number, min); + } + + + /** + * Clamp number to min and infinite bounds, inclusive.
+ * INTEGER version + * + * @param number clamped number + * @param min minimal allowed value + * @return result + */ + public static int clampi(Number number, Number min) + { + return (int) Math.round(clamp_double(number, min)); + } + + + /** + * Get class simple name + * + * @param obj object + * @return simple name + */ + public static String cname(Object obj) + { + if (obj == null) return "NULL"; + return obj.getClass().getSimpleName(); + } + + + /** + * Cube a double + * + * @param a squared double + * @return square + */ + public static double cube(double a) + { + return a * a * a; + } + + + /** + * Convert double to string, remove the mess at the end. + * + * @param d double + * @return string + */ + public static String doubleToString(double d) + { + String s = Double.toString(d); + s = s.replaceAll("([0-9]+\\.[0-9]+)00+[0-9]+", "$1"); + s = s.replaceAll("0+$", ""); + s = s.replaceAll("\\.$", ""); + return s; + } + + + /** + * Convert float to string, remove the mess at the end. + * + * @param f float + * @return string + */ + public static String floatToString(float f) + { + String s = Float.toString(f); + s = s.replaceAll("([0-9]+\\.[0-9]+)00+[0-9]+", "$1"); + s = s.replaceAll("0+$", ""); + s = s.replaceAll("\\.$", ""); + return s; + } + + + /** + * Check if number is in range + * + * @param number checked + * @param left lower end + * @param right upper end + * @return is in range + */ + public static boolean inRange(double number, double left, double right) + { + return number >= left && number <= right; + } + + + /** + * Get number from A to B at delta time (tween A to B) + * + * @param last last number + * @param now new number + * @param dtime delta time + * @return current number to render + */ + public static double interpolate(double last, double now, double dtime) + { + return last + (now - last) * dtime; + } + + + /** + * Get angle [degrees] from A to B at delta time (tween A to B) + * + * @param last last angle + * @param now new angle + * @param delta delta time + * @return current angle to render + */ + public static double interpolateDeg(double last, double now, double delta) + { + return Deg.norm(last + Deg.delta(now, last) * delta); + } + + + /** + * Get highest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static double max(double... numbers) + { + double highest = numbers[0]; + for (double num : numbers) { + if (num > highest) highest = num; + } + return highest; + } + + + /** + * Get highest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static float max(float... numbers) + { + float highest = numbers[0]; + for (float num : numbers) { + if (num > highest) highest = num; + } + return highest; + } + + + /** + * Get highest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static int max(int... numbers) + { + int highest = numbers[0]; + for (int num : numbers) { + if (num > highest) highest = num; + } + return highest; + } + + + /** + * Get lowest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static double min(double... numbers) + { + double lowest = numbers[0]; + for (double num : numbers) { + if (num < lowest) lowest = num; + } + return lowest; + } + + + /** + * Get lowest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static float min(float... numbers) + { + float lowest = numbers[0]; + for (float num : numbers) { + if (num < lowest) lowest = num; + } + return lowest; + } + + + /** + * Get lowest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static int min(int... numbers) + { + int lowest = numbers[0]; + for (int num : numbers) { + if (num < lowest) lowest = num; + } + return lowest; + } + + + /** + * Split comma separated list of integers. + * + * @param list String containing the list. + * @return array of integers or null. + */ + public static List parseIntList(String list) + { + if (list == null) { + return null; + } + String[] parts = list.split(","); + + ArrayList intList = new ArrayList(); + + for (String part : parts) { + try { + intList.add(Integer.parseInt(part)); + } catch (NumberFormatException e) {} + } + + return intList; + + } + + + /** + * Pick random element from a given list. + * + * @param list list of choices + * @return picked element + */ + public static Object pick(List list) + { + if (list.size() == 0) return null; + return list.get(rand.nextInt(list.size())); + } + + + /** + * Square a double + * + * @param a squared double + * @return square + */ + public static double square(double a) + { + return a * a; + } + + + /** + * Signum. + * + * @param number + * @return sign, -1,0,1 + */ + public static int sgn(double number) + { + return number > 0 ? 1 : number < 0 ? -1 : 0; + } + + + public static double frag(double d) + { + return d - Math.floor(d); + } + +} diff --git a/src/com/porcupine/math/Polar.java b/src/com/porcupine/math/Polar.java new file mode 100644 index 0000000..730f00a --- /dev/null +++ b/src/com/porcupine/math/Polar.java @@ -0,0 +1,107 @@ +package com.porcupine.math; + + +import com.porcupine.coord.Coord; + + +/** + * Polar coordinate + * + * @author MightyPork + */ +public class Polar { + + /** angle in radians */ + public double angle = 0; + /** distance in units */ + public double distance = 0; + + + /** + * @param angle angle in radians + * @param distance distance from origin + */ + public Polar(double angle, double distance) { + this.angle = angle; + this.distance = distance; + } + + + /** + * Make polar from coord + * + * @param coord coord + * @return polar + */ + public static Polar fromCoord(Coord coord) + { + return new Polar(Math.atan2(coord.y, coord.x), Math.sqrt(Calc.square(coord.x) + Calc.square(coord.y))); + } + + + /** + * Make polar from coords + * + * @param x x coord + * @param y y coord + * @return polar + */ + public static Polar fromCoord(double x, double y) + { + return Polar.fromCoord(new Coord(x, y)); + } + + + /** + * Make polar from coords + * + * @param x x coord + * @param z z coord + * @return polar + */ + public static Polar fromCoordXZ(double x, double z) + { + return Polar.fromCoordXZ(new Coord(x, 0, z)); + } + + + /** + * Get coord from polar + * + * @return coord + */ + public Coord toCoord() + { + return new Coord(distance * Math.cos(angle), distance * Math.sin(angle)); + } + + + /** + * Get X,0,Y coord from polar + * + * @return coord + */ + public Coord toCoordXZ() + { + return new Coord(distance * Math.cos(angle), 0, distance * Math.sin(angle)); + } + + + @Override + public String toString() + { + return "Polar(theta=" + angle + ", r=" + distance + ")"; + } + + + /** + * Build polar from X,Z instead of X,Y + * + * @param coord cpprd with X,Z + * @return polar + */ + public static Polar fromCoordXZ(Coord coord) + { + return fromCoord(coord.x, coord.z); + } +} diff --git a/src/com/porcupine/math/PolarDeg.java b/src/com/porcupine/math/PolarDeg.java new file mode 100644 index 0000000..2db6bb3 --- /dev/null +++ b/src/com/porcupine/math/PolarDeg.java @@ -0,0 +1,111 @@ +package com.porcupine.math; + + +import com.porcupine.coord.Coord; +import com.porcupine.math.Calc.Deg; +import com.porcupine.math.Calc.Rad; + + +/** + * Polar coordinate in degrees + * + * @author MightyPork + */ +public class PolarDeg { + + /** angle in degrees */ + public double angle = 0; + /** distance in units */ + public double distance = 0; + + + /** + * Polar coordinate in degrees + * + * @param angle angle in degrees + * @param distance distance from origin + */ + public PolarDeg(double angle, double distance) { + this.angle = angle; + this.distance = distance; + } + + + /** + * Make polar from coord + * + * @param coord coord + * @return polar + */ + public static PolarDeg fromCoord(Coord coord) + { + return new PolarDeg(Rad.toDeg(Math.atan2(coord.y, coord.x)), Math.sqrt(Calc.square(coord.x) + Calc.square(coord.y))); + } + + + /** + * Make polar from coords + * + * @param x x coord + * @param y y coord + * @return polar + */ + public static PolarDeg fromCoord(double x, double y) + { + return PolarDeg.fromCoord(new Coord(x, y)); + } + + + /** + * Make polar from coords + * + * @param x x coord + * @param z y coord + * @return polar + */ + public static PolarDeg fromCoordXZ(double x, double z) + { + return PolarDeg.fromCoordXZ(new Coord(x, 0, z)); + } + + + /** + * Get coord from polar + * + * @return coord + */ + public Coord toCoord() + { + return new Coord(distance * Math.cos(Deg.toRad(angle)), distance * Math.sin(Deg.toRad(angle))); + } + + + /** + * Get X,0,Y coord from polar + * + * @return coord + */ + public Coord toCoordXZ() + { + return new Coord(distance * Math.cos(Deg.toRad(angle)), 0, distance * Math.sin(Deg.toRad(angle))); + } + + + @Override + public String toString() + { + return "Polar(theta=" + angle + ", r=" + distance + ")"; + } + + + /** + * Build polar from X,Z instead of X,Y + * + * @param coord cpprd with X,Z + * @return polar + */ + public static PolarDeg fromCoordXZ(Coord coord) + { + return fromCoord(coord.x, coord.z); + } +} diff --git a/src/com/porcupine/math/Range.java b/src/com/porcupine/math/Range.java new file mode 100644 index 0000000..27e2996 --- /dev/null +++ b/src/com/porcupine/math/Range.java @@ -0,0 +1,197 @@ +package com.porcupine.math; + + +import java.util.Random; + + +/** + * Numeric range, able to generate random numbers and give min/max values. + * + * @author MightyPork + */ +public class Range { + + private double min = 0; + private double max = 1; + + private static Random rand = new Random(); + + + /** + * Implicit range constructor 0-1 + */ + public Range() {} + + + /** + * Create new range + * + * @param min min number + * @param max max number + */ + public Range(double min, double max) { + if (min > max) { + double t = min; + min = max; + max = t; + } + this.min = min; + this.max = max; + } + + + /** + * Create new range + * + * @param minmax min = max number + */ + public Range(double minmax) { + this.min = minmax; + this.max = minmax; + } + + + /** + * Get random integer from range + * + * @return random int + */ + public int randInt() + { + return (int) (Math.round(min) + rand.nextInt((int) (Math.round(max) - Math.round(min)) + 1)); + } + + + /** + * Get random double from this range + * + * @return random double + */ + public double randDouble() + { + return min + rand.nextDouble() * (max - min); + } + + + /** + * Get min + * + * @return min number + */ + public double getMin() + { + return min; + } + + + /** + * Get max + * + * @return max number + */ + public double getMax() + { + return max; + } + + + /** + * Get min + * + * @return min number + */ + public int getMinI() + { + return (int) min; + } + + + /** + * Get max + * + * @return max number + */ + public int getMaxI() + { + return (int) max; + } + + + /** + * Set min + * + * @param min min value + */ + public void setMin(double min) + { + this.min = min; + } + + + /** + * Set max + * + * @param max max value + */ + public void setMax(double max) + { + this.max = max; + } + + + @Override + public String toString() + { + return "Range(" + min + ";" + max + ")"; + } + + + /** + * Get identical copy + * + * @return copy + */ + public Range copy() + { + return new Range(min, max); + } + + + /** + * Set to value of other range + * + * @param other copied range + */ + public void setTo(Range other) + { + if (other == null) return; + min = other.min; + max = other.max; + + if (min > max) { + double t = min; + min = max; + max = t; + } + } + + + /** + * Set to min-max values + * + * @param min min value + * @param max max value + */ + public void setTo(double min, double max) + { + if (min > max) { + double t = min; + min = max; + max = t; + } + + this.min = min; + this.max = max; + } + +} diff --git a/src/com/porcupine/mutable/AbstractMutable.java b/src/com/porcupine/mutable/AbstractMutable.java new file mode 100644 index 0000000..f70e588 --- /dev/null +++ b/src/com/porcupine/mutable/AbstractMutable.java @@ -0,0 +1,60 @@ +package com.porcupine.mutable; + + +/** + * Mutable object + * + * @author MightyPork + * @param type + */ +public abstract class AbstractMutable { + + /** The wrapped value */ + public T o = getDefault(); + + + /** + * Implicint constructor + */ + public AbstractMutable() {} + + + /** + * new mutable object + * + * @param o value + */ + public AbstractMutable(T o) { + this.o = o; + } + + + /** + * Get the wrapped value + * + * @return value + */ + public T get() + { + return o; + } + + + /** + * Set value + * + * @param o new value to set + */ + public void set(T o) + { + this.o = o; + } + + + /** + * Get default value + * + * @return default value + */ + protected abstract T getDefault(); +} diff --git a/src/com/porcupine/mutable/MBoolean.java b/src/com/porcupine/mutable/MBoolean.java new file mode 100644 index 0000000..ba7848b --- /dev/null +++ b/src/com/porcupine/mutable/MBoolean.java @@ -0,0 +1,32 @@ +package com.porcupine.mutable; + + +/** + * Mutable boolean + * + * @author MightyPork + */ +public class MBoolean extends AbstractMutable { + + /** + * Mutable boolean + * + * @param o value + */ + public MBoolean(Boolean o) { + super(o); + } + + + /** + * Imp.c. + */ + public MBoolean() {} + + + @Override + protected Boolean getDefault() + { + return false; + } +} diff --git a/src/com/porcupine/mutable/MDouble.java b/src/com/porcupine/mutable/MDouble.java new file mode 100644 index 0000000..0c8e07f --- /dev/null +++ b/src/com/porcupine/mutable/MDouble.java @@ -0,0 +1,32 @@ +package com.porcupine.mutable; + + +/** + * Mutable double + * + * @author MightyPork + */ +public class MDouble extends AbstractMutable { + + /** + * Mutable double + * + * @param o value + */ + public MDouble(Double o) { + super(o); + } + + + /** + * Imp.c. + */ + public MDouble() {} + + + @Override + protected Double getDefault() + { + return 0d; + } +} diff --git a/src/com/porcupine/mutable/MFloat.java b/src/com/porcupine/mutable/MFloat.java new file mode 100644 index 0000000..2cf31f8 --- /dev/null +++ b/src/com/porcupine/mutable/MFloat.java @@ -0,0 +1,32 @@ +package com.porcupine.mutable; + + +/** + * Mutable float + * + * @author MightyPork + */ +public class MFloat extends AbstractMutable { + + /** + * Mutable float + * + * @param o value + */ + public MFloat(Float o) { + super(o); + } + + + /** + * Imp.c. + */ + public MFloat() {} + + + @Override + protected Float getDefault() + { + return 0f; + } +} diff --git a/src/com/porcupine/mutable/MInt.java b/src/com/porcupine/mutable/MInt.java new file mode 100644 index 0000000..090b963 --- /dev/null +++ b/src/com/porcupine/mutable/MInt.java @@ -0,0 +1,32 @@ +package com.porcupine.mutable; + + +/** + * Mutable integer + * + * @author MightyPork + */ +public class MInt extends AbstractMutable { + + /** + * Mutable int + * + * @param o value + */ + public MInt(Integer o) { + super(o); + } + + + /** + * Imp.c. + */ + public MInt() {} + + + @Override + protected Integer getDefault() + { + return 0; + } +} diff --git a/src/com/porcupine/mutable/MString.java b/src/com/porcupine/mutable/MString.java new file mode 100644 index 0000000..a81e24d --- /dev/null +++ b/src/com/porcupine/mutable/MString.java @@ -0,0 +1,32 @@ +package com.porcupine.mutable; + + +/** + * Mutable string + * + * @author MightyPork + */ +public class MString extends AbstractMutable { + + /** + * Mutable string + * + * @param o value + */ + public MString(String o) { + super(o); + } + + + /** + * Imp.c. + */ + public MString() {} + + + @Override + protected String getDefault() + { + return ""; + } +} diff --git a/src/com/porcupine/struct/Struct2.java b/src/com/porcupine/struct/Struct2.java new file mode 100644 index 0000000..8bf828f --- /dev/null +++ b/src/com/porcupine/struct/Struct2.java @@ -0,0 +1,154 @@ +package com.porcupine.struct; + + +import com.porcupine.math.Calc; + + +/** + * Structure of 2 objects. + * + * @author MightyPork + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + */ +public class Struct2 { + + /** + * 1st object + */ + public T1 a; + + /** + * 2nd object + */ + public T2 b; + + + /** + * Make structure of 2 objects + * + * @param objA 1st object + * @param objB 2nd object + */ + public Struct2(T1 objA, T2 objB) { + a = objA; + b = objB; + } + + + /** + * @return 1st object + */ + public T1 getA() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 getB() + { + return b; + } + + + /** + * @return 1st object + */ + public T1 get1() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 get2() + { + return b; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void setA(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void setB(T2 obj) + { + b = obj; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void set1(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void set2(T2 obj) + { + b = obj; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) { + return false; + } + + if (!this.getClass().equals(obj.getClass())) { + return false; + } + + Struct2 t = (Struct2) obj; + + return Calc.areObjectsEqual(a, t.a) && Calc.areObjectsEqual(b, t.b); + + } + + + @Override + public int hashCode() + { + int hash = 0; + hash += (a == null ? 0 : a.hashCode()); + hash += (b == null ? 0 : b.hashCode()); + return hash; + } + + + @Override + public String toString() + { + return "STRUCT {" + a + "," + b + "}"; + } + +} diff --git a/src/com/porcupine/struct/Struct3.java b/src/com/porcupine/struct/Struct3.java new file mode 100644 index 0000000..549c588 --- /dev/null +++ b/src/com/porcupine/struct/Struct3.java @@ -0,0 +1,203 @@ +package com.porcupine.struct; + + +import com.porcupine.math.Calc; + + +/** + * Structure of 3 objects. + * + * @author MightyPork + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + * @param 3rd object class + */ +public class Struct3 { + + /** + * 1st object + */ + public T1 a; + + /** + * 2nd object + */ + public T2 b; + + /** + * 3rd object + */ + public T3 c; + + + /** + * Make structure of 3 objects + * + * @param objA 1st object + * @param objB 2nd object + * @param objC 3rd object + */ + public Struct3(T1 objA, T2 objB, T3 objC) { + a = objA; + b = objB; + c = objC; + } + + + /** + * @return 1st object + */ + public T1 getA() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 getB() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 getC() + { + return c; + } + + + /** + * @return 1st object + */ + public T1 get1() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 get2() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 get3() + { + return c; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void setA(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void setB(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void setC(T3 obj) + { + c = obj; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void set1(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void set2(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void set3(T3 obj) + { + c = obj; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) { + return false; + } + + if (!this.getClass().equals(obj.getClass())) { + return false; + } + + Struct3 t = (Struct3) obj; + + return Calc.areObjectsEqual(a, t.a) && Calc.areObjectsEqual(b, t.b) && Calc.areObjectsEqual(c, t.c); + + } + + + @Override + public int hashCode() + { + int hash = 0; + hash += (a == null ? 0 : a.hashCode()); + hash += (b == null ? 0 : b.hashCode()); + hash += (c == null ? 0 : c.hashCode()); + return hash; + } + + + @Override + public String toString() + { + return "STRUCT {" + a + "," + b + "," + c + "}"; + } + +} diff --git a/src/com/porcupine/struct/Struct4.java b/src/com/porcupine/struct/Struct4.java new file mode 100644 index 0000000..9aa6ba1 --- /dev/null +++ b/src/com/porcupine/struct/Struct4.java @@ -0,0 +1,252 @@ +package com.porcupine.struct; + + +import com.porcupine.math.Calc; + + +/** + * Structure of 4 objects. + * + * @author MightyPork + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + * @param 3rd object class + * @param 4th object class + */ +public class Struct4 { + + /** + * 1st object + */ + public T1 a; + + /** + * 2nd object + */ + public T2 b; + + /** + * 3rd object + */ + public T3 c; + + /** + * 4th object + */ + public T4 d; + + + /** + * Make structure of 4 objects + * + * @param objA 1st object + * @param objB 2nd object + * @param objC 3rd object + * @param objD 4th object + */ + public Struct4(T1 objA, T2 objB, T3 objC, T4 objD) { + a = objA; + b = objB; + c = objC; + d = objD; + } + + + /** + * @return 1st object + */ + public T1 getA() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 getB() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 getC() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 getD() + { + return d; + } + + + /** + * @return 1st object + */ + public T1 get1() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 get2() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 get3() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 get4() + { + return d; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void setA(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void setB(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void setC(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void setD(T4 obj) + { + d = obj; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void set1(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void set2(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void set3(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void set4(T4 obj) + { + d = obj; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) { + return false; + } + + if (!this.getClass().equals(obj.getClass())) { + return false; + } + + Struct4 t = (Struct4) obj; + + return Calc.areObjectsEqual(a, t.a) && Calc.areObjectsEqual(b, t.b) && Calc.areObjectsEqual(c, t.c) && Calc.areObjectsEqual(d, t.d); + + } + + + @Override + public int hashCode() + { + int hash = 0; + hash += (a == null ? 0 : a.hashCode()); + hash += (b == null ? 0 : b.hashCode()); + hash += (c == null ? 0 : c.hashCode()); + hash += (d == null ? 0 : d.hashCode()); + return hash; + } + + + @Override + public String toString() + { + return "STRUCT {" + a + "," + b + "," + c + "," + d + "}"; + } + +} diff --git a/src/com/porcupine/struct/Struct5.java b/src/com/porcupine/struct/Struct5.java new file mode 100644 index 0000000..46533ba --- /dev/null +++ b/src/com/porcupine/struct/Struct5.java @@ -0,0 +1,301 @@ +package com.porcupine.struct; + + +import com.porcupine.math.Calc; + + +/** + * Structure of 5 objects. + * + * @author MightyPork + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + * @param 3rd object class + * @param 4th object class + * @param 5th object class + */ +public class Struct5 { + + /** + * 1st object + */ + public T1 a; + + /** + * 2nd object + */ + public T2 b; + + /** + * 3rd object + */ + public T3 c; + + /** + * 4th object + */ + public T4 d; + + /** + * 5th object + */ + public T5 e; + + + /** + * Make structure of 4 objects + * + * @param objA 1st object + * @param objB 2nd object + * @param objC 3rd object + * @param objD 4th object + * @param objE 5th object + */ + public Struct5(T1 objA, T2 objB, T3 objC, T4 objD, T5 objE) { + a = objA; + b = objB; + c = objC; + d = objD; + e = objE; + } + + + /** + * @return 1st object + */ + public T1 getA() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 getB() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 getC() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 getD() + { + return d; + } + + + /** + * @return 5th object + */ + public T5 getE() + { + return e; + } + + + /** + * @return 1st object + */ + public T1 get1() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 get2() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 get3() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 get4() + { + return d; + } + + + /** + * @return 5th object + */ + public T5 get5() + { + return e; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void setA(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void setB(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void setC(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void setD(T4 obj) + { + d = obj; + } + + + /** + * Set 5th object + * + * @param obj 5th object + */ + public void setE(T5 obj) + { + e = obj; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void set1(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void set2(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void set3(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void set4(T4 obj) + { + d = obj; + } + + + /** + * Set 5th object + * + * @param obj 5th object + */ + public void set5(T5 obj) + { + e = obj; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) { + return false; + } + + if (!this.getClass().equals(obj.getClass())) { + return false; + } + + Struct5 t = (Struct5) obj; + + return Calc.areObjectsEqual(a, t.a) && Calc.areObjectsEqual(b, t.b) && Calc.areObjectsEqual(c, t.c) && Calc.areObjectsEqual(d, t.d) && Calc.areObjectsEqual(e, t.e); + + } + + + @Override + public int hashCode() + { + int hash = 0; + hash += (a == null ? 0 : a.hashCode()); + hash += (b == null ? 0 : b.hashCode()); + hash += (c == null ? 0 : c.hashCode()); + hash += (d == null ? 0 : d.hashCode()); + hash += (e == null ? 0 : e.hashCode()); + return hash; + } + + + @Override + public String toString() + { + return "STRUCT {" + a + "," + b + "," + c + "," + d + "," + e + "}"; + } + +} diff --git a/src/com/porcupine/struct/Struct6.java b/src/com/porcupine/struct/Struct6.java new file mode 100644 index 0000000..a82f558 --- /dev/null +++ b/src/com/porcupine/struct/Struct6.java @@ -0,0 +1,351 @@ +package com.porcupine.struct; + + +import com.porcupine.math.Calc; + + +/** + * Structure of 6 objects. + * + * @author MightyPork + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + * @param 3rd object class + * @param 4th object class + * @param 5th object class + * @param 6th object class + */ +public class Struct6 { + + /** + * 1st object + */ + public T1 a; + + /** + * 2nd object + */ + public T2 b; + + /** + * 3rd object + */ + public T3 c; + + /** + * 4th object + */ + public T4 d; + + /** + * 5th object + */ + public T5 e; + + /** + * 6th object + */ + public T6 f; + + + /** + * Make structure of 4 objects + * + * @param objA 1st object + * @param objB 2nd object + * @param objC 3rd object + * @param objD 4th object + * @param objE 5th object + * @param objF 6th object + */ + public Struct6(T1 objA, T2 objB, T3 objC, T4 objD, T5 objE, T6 objF) { + a = objA; + b = objB; + c = objC; + d = objD; + e = objE; + f = objF; + } + + + /** + * @return 1st object + */ + public T1 getA() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 getB() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 getC() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 getD() + { + return d; + } + + + /** + * @return 5th object + */ + public T5 getE() + { + return e; + } + + + /** + * @return 6th object + */ + public T6 getF() + { + return f; + } + + + /** + * @return 1st object + */ + public T1 get1() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 get2() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 get3() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 get4() + { + return d; + } + + + /** + * @return 5th object + */ + public T5 get5() + { + return e; + } + + + /** + * @return 6th object + */ + public T6 get6() + { + return f; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void setA(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void setB(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void setC(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void setD(T4 obj) + { + d = obj; + } + + + /** + * Set 5th object + * + * @param obj 5th object + */ + public void setE(T5 obj) + { + e = obj; + } + + + /** + * Set 6th object + * + * @param obj 6th object + */ + public void setF(T6 obj) + { + f = obj; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void set1(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void set2(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void set3(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void set4(T4 obj) + { + d = obj; + } + + + /** + * Set 5th object + * + * @param obj 5th object + */ + public void set5(T5 obj) + { + e = obj; + } + + + /** + * Set 6th object + * + * @param obj 6th object + */ + public void set6(T6 obj) + { + f = obj; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) { + return false; + } + + if (!this.getClass().equals(obj.getClass())) { + return false; + } + + Struct6 t = (Struct6) obj; + + return Calc.areObjectsEqual(a, t.a) && Calc.areObjectsEqual(b, t.b) && Calc.areObjectsEqual(c, t.c) && Calc.areObjectsEqual(d, t.d) && Calc.areObjectsEqual(e, t.e) + && Calc.areObjectsEqual(f, t.f); + + } + + + @Override + public int hashCode() + { + int hash = 0; + hash += (a == null ? 0 : a.hashCode()); + hash += (b == null ? 0 : b.hashCode()); + hash += (c == null ? 0 : c.hashCode()); + hash += (d == null ? 0 : d.hashCode()); + hash += (e == null ? 0 : e.hashCode()); + hash += (f == null ? 0 : f.hashCode()); + return hash; + } + + + @Override + public String toString() + { + return "STRUCT {" + a + "," + b + "," + c + "," + d + "," + e + "," + f + "}"; + } + +} diff --git a/src/com/porcupine/struct/Struct7.java b/src/com/porcupine/struct/Struct7.java new file mode 100644 index 0000000..53e4378 --- /dev/null +++ b/src/com/porcupine/struct/Struct7.java @@ -0,0 +1,400 @@ +package com.porcupine.struct; + + +import com.porcupine.math.Calc; + + +/** + * Structure of 7 objects. + * + * @author MightyPork + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + * @param 3rd object class + * @param 4th object class + * @param 5th object class + * @param 6th object class + * @param 7th object class + */ +public class Struct7 { + + /** + * 1st object + */ + public T1 a; + + /** + * 2nd object + */ + public T2 b; + + /** + * 3rd object + */ + public T3 c; + + /** + * 4th object + */ + public T4 d; + + /** + * 5th object + */ + public T5 e; + + /** + * 6th object + */ + public T6 f; + + /** + * 7th object + */ + public T7 g; + + + /** + * Make structure of 4 objects + * + * @param objA 1st object + * @param objB 2nd object + * @param objC 3rd object + * @param objD 4th object + * @param objE 5th object + * @param objF 6th object + * @param objG 7th object + */ + public Struct7(T1 objA, T2 objB, T3 objC, T4 objD, T5 objE, T6 objF, T7 objG) { + a = objA; + b = objB; + c = objC; + d = objD; + e = objE; + f = objF; + g = objG; + } + + + /** + * @return 1st object + */ + public T1 getA() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 getB() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 getC() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 getD() + { + return d; + } + + + /** + * @return 5th object + */ + public T5 getE() + { + return e; + } + + + /** + * @return 6th object + */ + public T6 getF() + { + return f; + } + + + /** + * @return 7th object + */ + public T7 getG() + { + return g; + } + + + /** + * @return 1st object + */ + public T1 get1() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 get2() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 get3() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 get4() + { + return d; + } + + + /** + * @return 5th object + */ + public T5 get5() + { + return e; + } + + + /** + * @return 6th object + */ + public T6 get6() + { + return f; + } + + + /** + * @return 7th object + */ + public T7 get7() + { + return g; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void setA(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void setB(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void setC(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void setD(T4 obj) + { + d = obj; + } + + + /** + * Set 5th object + * + * @param obj 5th object + */ + public void setE(T5 obj) + { + e = obj; + } + + + /** + * Set 6th object + * + * @param obj 6th object + */ + public void setF(T6 obj) + { + f = obj; + } + + + /** + * Set 7th object + * + * @param obj 6th object + */ + public void setG(T7 obj) + { + g = obj; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void set1(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void set2(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void set3(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void set4(T4 obj) + { + d = obj; + } + + + /** + * Set 5th object + * + * @param obj 5th object + */ + public void set5(T5 obj) + { + e = obj; + } + + + /** + * Set 6th object + * + * @param obj 6th object + */ + public void set6(T6 obj) + { + f = obj; + } + + + /** + * Set 7th object + * + * @param obj 6th object + */ + public void set7(T7 obj) + { + g = obj; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) { + return false; + } + + if (!this.getClass().equals(obj.getClass())) { + return false; + } + + Struct7 t = (Struct7) obj; + + return Calc.areObjectsEqual(a, t.a) && Calc.areObjectsEqual(b, t.b) && Calc.areObjectsEqual(c, t.c) && Calc.areObjectsEqual(d, t.d) && Calc.areObjectsEqual(e, t.e) + && Calc.areObjectsEqual(f, t.f) && Calc.areObjectsEqual(g, t.g); + + } + + + @Override + public int hashCode() + { + int hash = 0; + hash += (a == null ? 0 : a.hashCode()); + hash += (b == null ? 0 : b.hashCode()); + hash += (c == null ? 0 : c.hashCode()); + hash += (d == null ? 0 : d.hashCode()); + hash += (e == null ? 0 : e.hashCode()); + hash += (f == null ? 0 : f.hashCode()); + hash += (g == null ? 0 : g.hashCode()); + return hash; + } + + + @Override + public String toString() + { + return "STRUCT {" + a + "," + b + "," + c + "," + d + "," + e + "," + f + "," + g + "}"; + } + +} diff --git a/src/com/porcupine/struct/Struct8.java b/src/com/porcupine/struct/Struct8.java new file mode 100644 index 0000000..cfaecdc --- /dev/null +++ b/src/com/porcupine/struct/Struct8.java @@ -0,0 +1,449 @@ +package com.porcupine.struct; + + +import com.porcupine.math.Calc; + + +/** + * Structure of 7 objects. + * + * @author MightyPork + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + * @param 3rd object class + * @param 4th object class + * @param 5th object class + * @param 6th object class + * @param 7th object class + * @param 8th object class + */ +public class Struct8 { + + /** + * 1st object + */ + public T1 a; + + /** + * 2nd object + */ + public T2 b; + + /** + * 3rd object + */ + public T3 c; + + /** + * 4th object + */ + public T4 d; + + /** + * 5th object + */ + public T5 e; + + /** + * 6th object + */ + public T6 f; + + /** + * 7th object + */ + public T7 g; + + /** + * 8th object + */ + public T8 h; + + + /** + * Make structure of 4 objects + * + * @param objA 1st object + * @param objB 2nd object + * @param objC 3rd object + * @param objD 4th object + * @param objE 5th object + * @param objF 6th object + * @param objG 7th object + * @param objH 8th object + */ + public Struct8(T1 objA, T2 objB, T3 objC, T4 objD, T5 objE, T6 objF, T7 objG, T8 objH) { + a = objA; + b = objB; + c = objC; + d = objD; + e = objE; + f = objF; + g = objG; + h = objH; + } + + + /** + * @return 1st object + */ + public T1 getA() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 getB() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 getC() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 getD() + { + return d; + } + + + /** + * @return 5th object + */ + public T5 getE() + { + return e; + } + + + /** + * @return 6th object + */ + public T6 getF() + { + return f; + } + + + /** + * @return 7th object + */ + public T7 getG() + { + return g; + } + + + /** + * @return 8th object + */ + public T8 getH() + { + return h; + } + + + /** + * @return 1st object + */ + public T1 get1() + { + return a; + } + + + /** + * @return 2nd object + */ + public T2 get2() + { + return b; + } + + + /** + * @return 3rd object + */ + public T3 get3() + { + return c; + } + + + /** + * @return 4th object + */ + public T4 get4() + { + return d; + } + + + /** + * @return 5th object + */ + public T5 get5() + { + return e; + } + + + /** + * @return 6th object + */ + public T6 get6() + { + return f; + } + + + /** + * @return 7th object + */ + public T7 get7() + { + return g; + } + + + /** + * @return 8th object + */ + public T8 get8() + { + return h; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void setA(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void setB(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void setC(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void setD(T4 obj) + { + d = obj; + } + + + /** + * Set 5th object + * + * @param obj 5th object + */ + public void setE(T5 obj) + { + e = obj; + } + + + /** + * Set 6th object + * + * @param obj 6th object + */ + public void setF(T6 obj) + { + f = obj; + } + + + /** + * Set 7th object + * + * @param obj 6th object + */ + public void setG(T7 obj) + { + g = obj; + } + + + /** + * Set 8th object + * + * @param obj 6th object + */ + public void setH(T8 obj) + { + h = obj; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void set1(T1 obj) + { + a = obj; + } + + + /** + * Set 2nd object + * + * @param obj 2nd object + */ + public void set2(T2 obj) + { + b = obj; + } + + + /** + * Set 3rd object + * + * @param obj 3rd object + */ + public void set3(T3 obj) + { + c = obj; + } + + + /** + * Set 4th object + * + * @param obj 4th object + */ + public void set4(T4 obj) + { + d = obj; + } + + + /** + * Set 5th object + * + * @param obj 5th object + */ + public void set5(T5 obj) + { + e = obj; + } + + + /** + * Set 6th object + * + * @param obj 6th object + */ + public void set6(T6 obj) + { + f = obj; + } + + + /** + * Set 7th object + * + * @param obj 6th object + */ + public void set7(T7 obj) + { + g = obj; + } + + + /** + * Set 8th object + * + * @param obj 6th object + */ + public void set8(T8 obj) + { + h = obj; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) { + return false; + } + + if (!this.getClass().equals(obj.getClass())) { + return false; + } + + Struct8 t = (Struct8) obj; + + return Calc.areObjectsEqual(a, t.a) && Calc.areObjectsEqual(b, t.b) && Calc.areObjectsEqual(c, t.c) && Calc.areObjectsEqual(d, t.d) && Calc.areObjectsEqual(e, t.e) + && Calc.areObjectsEqual(f, t.f) && Calc.areObjectsEqual(g, t.g) && Calc.areObjectsEqual(h, t.h); + + } + + + @Override + public int hashCode() + { + int hash = 0; + hash += (a == null ? 0 : a.hashCode()); + hash += (b == null ? 0 : b.hashCode()); + hash += (c == null ? 0 : c.hashCode()); + hash += (d == null ? 0 : d.hashCode()); + hash += (e == null ? 0 : e.hashCode()); + hash += (f == null ? 0 : f.hashCode()); + hash += (g == null ? 0 : g.hashCode()); + hash += (h == null ? 0 : h.hashCode()); + return hash; + } + + + @Override + public String toString() + { + return "STRUCT {" + a + "," + b + "," + c + "," + d + "," + e + "," + f + "," + g + "," + h + "}"; + } + +} diff --git a/src/com/porcupine/time/FpsMeter.java b/src/com/porcupine/time/FpsMeter.java new file mode 100644 index 0000000..18129d2 --- /dev/null +++ b/src/com/porcupine/time/FpsMeter.java @@ -0,0 +1,62 @@ +package com.porcupine.time; + + +/** + * Class for counting FPS in games.
+ * This class can be used also as a simple frequency meter - output is in Hz. + * + * @author MightyPork + */ +public class FpsMeter { + + private long frames = 0; + private long drops = 0; + private long lastTimeMillis = System.currentTimeMillis(); + private long lastSecFPS = 0; + private long lastSecDrop = 0; + + + /** + * @return current second's FPS + */ + public long getFPS() + { + return lastSecFPS; + } + + + /** + * Notification that frame was rendered + */ + public void frame() + { + if (System.currentTimeMillis() - lastTimeMillis > 1000) { + lastSecFPS = frames; + lastSecDrop = drops; + frames = 0; + drops = 0; + lastTimeMillis = System.currentTimeMillis(); + } + frames++; + } + + + /** + * Notification that some frames have been dropped + * + * @param dropped dropped frames + */ + public void drop(int dropped) + { + drops += dropped; + } + + + /** + * @return current second's dropped frames + */ + public long getDropped() + { + return lastSecDrop; + } +} diff --git a/src/com/porcupine/time/TimerDelta.java b/src/com/porcupine/time/TimerDelta.java new file mode 100644 index 0000000..ce00a83 --- /dev/null +++ b/src/com/porcupine/time/TimerDelta.java @@ -0,0 +1,47 @@ +package com.porcupine.time; + + +/** + * Timer for delta timing + * + * @author MightyPork + */ +public class TimerDelta { + + private long lastFrame; + + private static final long SECOND = 1000000000; // a million nanoseconds + + + /** + * New delta timer + */ + public TimerDelta() { + lastFrame = System.nanoTime(); + } + + + /** + * Get current time in NS + * + * @return current time NS + */ + public long getTime() + { + return System.nanoTime(); + } + + + /** + * Get time since the last "getDelta()" call. + * + * @return delta time (seconds) + */ + public double getDelta() + { + long time = getTime(); + double delta = (time - lastFrame) / (double) SECOND; + lastFrame = time; + return delta; + } +} diff --git a/src/com/porcupine/time/TimerInterpolating.java b/src/com/porcupine/time/TimerInterpolating.java new file mode 100644 index 0000000..6b24bdc --- /dev/null +++ b/src/com/porcupine/time/TimerInterpolating.java @@ -0,0 +1,105 @@ +package com.porcupine.time; + + +/** + * Timer for interpolated timing + * + * @author MightyPork + */ +public class TimerInterpolating { + + private long lastFrame = 0; + private long nextFrame = 0; + private long skipped = 0; + private long lastSkipped = 0; + + private static final long SECOND = 1000000000; // a million nanoseconds + private long FRAME; // a time of one frame in nanoseconds + + + /** + * New interpolated timer + * + * @param fps target FPS + */ + public TimerInterpolating(long fps) { + FRAME = Math.round(SECOND / fps); + + lastFrame = System.nanoTime(); + nextFrame = System.nanoTime() + FRAME; + } + + + /** + * Sync and calculate dropped frames etc. + */ + public void sync() + { + long time = getTime(); + if (time >= nextFrame) { + long skippedNow = (long) Math.floor((time - nextFrame) / (double) FRAME) + 1; + //System.out.println("Skipping: "+skippedNow); + skipped += skippedNow; + lastFrame = nextFrame + (1 - skippedNow) * FRAME; + nextFrame += skippedNow * FRAME; + } + } + + + /** + * Get nanotime + * + * @return nanotime + */ + public long getTime() + { + return System.nanoTime(); + } + + + /** + * Get fraction of next frame + * + * @return fraction + */ + public double getFraction() + { + if (getSkipped() >= 1) { + return 1; + } + + long time = getTime(); + + if (time <= nextFrame) { + return (double) (time - lastFrame) / (double) FRAME; + } + + return 1; + } + + + /** + * Get number of elapsed ticks + * + * @return ticks + */ + public int getSkipped() + { + long change = skipped - lastSkipped; + lastSkipped = skipped; + return (int) change; + } + + + /** + * Clear timer and start counting new tick. + */ + public void startNewFrame() + { + //System.out.println("! start new frame !"); + long time = getTime(); + lastFrame = time; + nextFrame = time + FRAME; + lastSkipped = skipped; + } +} diff --git a/src/com/porcupine/util/FileSuffixFilter.java b/src/com/porcupine/util/FileSuffixFilter.java new file mode 100644 index 0000000..40354f3 --- /dev/null +++ b/src/com/porcupine/util/FileSuffixFilter.java @@ -0,0 +1,39 @@ +package com.porcupine.util; + + +import java.io.File; +import java.io.FileFilter; + + +/** + * File filter for certain suffixes + * + * @author MightyPork + */ +public class FileSuffixFilter implements FileFilter { + + /** Array of allowed suffixes */ + private String[] suffixes = null; + + + /** + * Suffix filter + * + * @param suffixes var-args allowed suffixes, case insensitive + */ + public FileSuffixFilter(String... suffixes) { + this.suffixes = suffixes; + } + + + @Override + public boolean accept(File pathname) + { + //System.out.println(pathname); + for (String suffix : suffixes) { + return pathname.isFile() && pathname.getName().toLowerCase().trim().endsWith(suffix.toLowerCase().trim()); + } + return false; + } + +} diff --git a/src/com/porcupine/util/FileUtils.java b/src/com/porcupine/util/FileUtils.java new file mode 100644 index 0000000..2bfcc76 --- /dev/null +++ b/src/com/porcupine/util/FileUtils.java @@ -0,0 +1,197 @@ +package com.porcupine.util; + + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import net.tortuga.util.Log; + + +/** + * Utilities for filesystem + * + * @author MightyPork + */ +public class FileUtils { + + private enum EnumOS + { + linux, solaris, windows, macos, unknown; + } + + + /** + * get working directory path ending with slash + * + * @param dirname name of the directory, dot will be added automatically + * @return File path to the folder + */ + public static File getAppDir(String dirname) + { + String s = System.getProperty("user.home", "."); + File file; + + switch (getOs()) { + case linux: + case solaris: + file = new File(s, "." + dirname + '/'); + break; + + case windows: + String s1 = System.getenv("APPDATA"); + + if (s1 != null) { + file = new File(s1, "." + dirname + '/'); + } else { + file = new File(s, "." + dirname + '/'); + } + + break; + + case macos: + file = new File(s, "Library/Application Support/" + dirname); + break; + + default: + file = new File(s, dirname + "/"); + break; + } + + if (!file.exists() && !file.mkdirs()) { + throw new RuntimeException((new StringBuilder()).append("The working directory could not be created: ").append(file).toString()); + } else { + return file; + } + } + + + private static EnumOS getOs() + { + String s = System.getProperty("os.name").toLowerCase(); + + if (s.contains("win")) { + return EnumOS.windows; + } + + if (s.contains("mac")) { + return EnumOS.macos; + } + + if (s.contains("solaris")) { + return EnumOS.solaris; + } + + if (s.contains("sunos")) { + return EnumOS.solaris; + } + + if (s.contains("linux")) { + return EnumOS.linux; + } + + if (s.contains("unix")) { + return EnumOS.linux; + } else { + return EnumOS.unknown; + } + } + + + /** + * Get files in a folder (create folder if needed) + * + * @param dir folder + * @param filter file filter + * @return list of files + */ + public static List listFolder(File dir, FileFilter filter) + { + try { + dir.mkdir(); + } catch (RuntimeException e) { + Log.e("Error creating folder " + dir, e); + } + + List list = new ArrayList(); + + try { + for (File f : dir.listFiles(filter)) { + list.add(f); + } + } catch (Exception e) { + Log.e("Error listing folder " + dir, e); + } + + return list; + } + + + /** + * Get files in a folder (create folder if needed) + * + * @param dir folder + * @return list of files + */ + public static List listFolder(File dir) + { + return listFolder(dir, null); + } + + + /** + * Remove extension. + * + * @param file file + * @return filename without extension + */ + public static String removeExtension(File file) + { + return removeExtension(file.getName()); + } + + + /** + * Remove extension. + * + * @param filename + * @return filename without extension + */ + public static String removeExtension(String filename) + { + String[] parts = filename.split("[.]"); + String out = ""; + for (int i = 0; i < parts.length - 1; i++) { + out += parts[i]; + } + + return out; + } + + + /** + * Read entire file to a string. + * + * @param file file + * @return file contents + * @throws IOException + */ + public static String fileToString(File file) throws IOException + { + String result = null; + DataInputStream in = null; + + byte[] buffer = new byte[(int) file.length()]; + in = new DataInputStream(new FileInputStream(file)); + in.readFully(buffer); + result = new String(buffer); + in.close(); + + return result; + } + +} diff --git a/src/com/porcupine/util/PropertyManager.java b/src/com/porcupine/util/PropertyManager.java new file mode 100644 index 0000000..420eb4c --- /dev/null +++ b/src/com/porcupine/util/PropertyManager.java @@ -0,0 +1,1150 @@ +package com.porcupine.util; + + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; + +import org.lwjgl.input.Keyboard; + +import com.porcupine.math.Calc; + + +/** + * Property manager with advanced formatting and value checking.
+ * Methods starting with put are for filling. Most of the others are shortcuts + * to getters. + * + * @author MightyPork + */ +public class PropertyManager { + + /** + * Properties stored in file, alphabetically sorted.
+ * Property file is much cleaner than the normal java.util.Properties, + * newlines can be inserted to separate categories, and individual keys can + * have their own inline comments. + * + * @author MightyPork + * @copy (c) 2012 + */ + private static class PC_SortedProperties extends Properties { + + /** A table of hex digits */ + private static final char[] hexDigit_custom = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + + /** + * this is here because the original method is private. + * + * @param nibble + * @return hex char. + */ + private static char toHex_custom(int nibble) + { + return hexDigit_custom[(nibble & 0xF)]; + } + + + private static void writeComments_custom(BufferedWriter bw, String comm) throws IOException + { + String comments = comm.replace("\n\n", "\n \n"); + + int len = comments.length(); + int current = 0; + int last = 0; + char[] uu = new char[6]; + uu[0] = '\\'; + uu[1] = 'u'; + while (current < len) { + char c = comments.charAt(current); + if (c > '\u00ff' || c == '\n' || c == '\r') { + if (last != current) { + bw.write("# " + comments.substring(last, current)); + } + + if (c > '\u00ff') { + uu[2] = toHex_custom((c >> 12) & 0xf); + uu[3] = toHex_custom((c >> 8) & 0xf); + uu[4] = toHex_custom((c >> 4) & 0xf); + uu[5] = toHex_custom(c & 0xf); + bw.write(new String(uu)); + } else { + bw.newLine(); + if (c == '\r' && current != len - 1 && comments.charAt(current + 1) == '\n') { + current++; + } + // if (current == len - 1 || (comments.charAt(current + 1) != '#' && comments.charAt(current + 1) != '!')) + // bw.write("#"); + } + last = current + 1; + } + current++; + } + if (last != current) { + bw.write("# " + comments.substring(last, current)); + } + bw.newLine(); + bw.newLine(); + bw.newLine(); + } + + /** Option: put empty line before each comment. */ + public boolean cfgEmptyLineBeforeComment = true; + /** + * Option: Separate sections by newline
+ * Section = string before first dot in key. + */ + public boolean cfgSeparateSectionsByEmptyLine = true; + + private boolean firstEntry = true; + + private Hashtable keyComments = new Hashtable(); + + private String lastSectionBeginning = ""; + + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public synchronized Enumeration keys() + { + Enumeration keysEnum = super.keys(); + Vector keyList = new Vector(); + while (keysEnum.hasMoreElements()) { + keyList.add(keysEnum.nextElement()); + } + Collections.sort(keyList); + return keyList.elements(); + } + + + private String saveConvert_custom(String theString, boolean escapeSpace, boolean escapeUnicode) + { + int len = theString.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuffer outBuffer = new StringBuffer(bufLen); + + for (int x = 0; x < len; x++) { + char aChar = theString.charAt(x); + // Handle common case first, selecting largest block that + // avoids the specials below + if ((aChar > 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); + outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + switch (aChar) { + case ' ': + if (x == 0 || escapeSpace) { + outBuffer.append('\\'); + } + outBuffer.append(' '); + break; + case '\t': + outBuffer.append('\\'); + outBuffer.append('t'); + break; + case '\n': + outBuffer.append('\\'); + outBuffer.append('n'); + break; + case '\r': + outBuffer.append('\\'); + outBuffer.append('r'); + break; + case '\f': + outBuffer.append('\\'); + outBuffer.append('f'); + break; + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + outBuffer.append('\\'); + outBuffer.append(aChar); + break; + default: + if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode) { + outBuffer.append('\\'); + outBuffer.append('u'); + outBuffer.append(toHex_custom((aChar >> 12) & 0xF)); + outBuffer.append(toHex_custom((aChar >> 8) & 0xF)); + outBuffer.append(toHex_custom((aChar >> 4) & 0xF)); + outBuffer.append(toHex_custom(aChar & 0xF)); + } else { + outBuffer.append(aChar); + } + } + } + return outBuffer.toString(); + } + + + /** + * Set additional comment to a key + * + * @param key key for comment + * @param comment the comment + */ + public void setKeyComment(String key, String comment) + { + keyComments.put(key, comment); + } + + + @Override + public void store(OutputStream out, String comments) throws IOException + { + store_custom(new BufferedWriter(new OutputStreamWriter(out, "UTF-8")), comments, false); + } + + + @SuppressWarnings("rawtypes") + private void store_custom(BufferedWriter bw, String comments, boolean escUnicode) throws IOException + { + if (comments != null) { + writeComments_custom(bw, comments); + } + synchronized (this) { + for (Enumeration e = keys(); e.hasMoreElements();) { + boolean wasNewLine = false; + + String key = (String) e.nextElement(); + String val = (String) get(key); + key = saveConvert_custom(key, true, escUnicode); + val = saveConvert_custom(val, false, escUnicode); + + if (cfgSeparateSectionsByEmptyLine && !lastSectionBeginning.equals(key.split("[.]")[0])) { + if (!firstEntry) { + bw.newLine(); + bw.newLine(); + } + + wasNewLine = true; + lastSectionBeginning = key.split("[.]")[0]; + } + + if (keyComments.containsKey(key)) { + String cm = keyComments.get(key); + cm = cm.replace("\r", "\n"); + cm = cm.replace("\r\n", "\n"); + cm = cm.replace("\n\n", "\n \n"); + + String[] cmlines = cm.split("\n"); + + if (!wasNewLine && !firstEntry && cfgEmptyLineBeforeComment) { + bw.newLine(); + } + for (String cmline : cmlines) { + bw.write("# " + cmline); + bw.newLine(); + } + } + + bw.write(key + " = " + val); + bw.newLine(); + + firstEntry = false; + } + } + bw.flush(); + } + } + + /** + * Helper class which loads Properties from UTF-8 file (Properties use + * "ISO-8859-1" by default) + * + * @author Itay Maman + */ + private static class PropertiesLoader { + + private static String escapifyStr(String str) + { + StringBuilder result = new StringBuilder(); + + int len = str.length(); + for (int x = 0; x < len; x++) { + char ch = str.charAt(x); + if (ch <= 0x007e) { + result.append(ch); + continue; + } + + result.append('\\'); + result.append('u'); + result.append(hexDigit(ch, 12)); + result.append(hexDigit(ch, 8)); + result.append(hexDigit(ch, 4)); + result.append(hexDigit(ch, 0)); + } + return result.toString(); + } + + + private static char hexDigit(char ch, int offset) + { + int val = (ch >> offset) & 0xF; + if (val <= 9) { + return (char) ('0' + val); + } + + return (char) ('A' + val - 10); + } + + + public static PC_SortedProperties loadProperties(PC_SortedProperties props, InputStream is) throws IOException + { + return loadProperties(props, is, "utf-8"); + } + + + public static PC_SortedProperties loadProperties(PC_SortedProperties props, InputStream is, String encoding) throws IOException + { + StringBuilder sb = new StringBuilder(); + InputStreamReader isr = new InputStreamReader(is, encoding); + while (true) { + int temp = isr.read(); + if (temp < 0) { + break; + } + + char c = (char) temp; + sb.append(c); + } + + String read = sb.toString(); + + String inputString = escapifyStr(read); + byte[] bs = inputString.getBytes("ISO-8859-1"); + ByteArrayInputStream bais = new ByteArrayInputStream(bs); + + PC_SortedProperties ps = props; + ps.load(bais); + return ps; + } + } + + /** + * Property entry in Property manager. + * + * @author MightyPork + * @copy (c) 2012 + */ + private class Property { + + public boolean bool = false; + public String entryComment; + public boolean defbool = false; + public double defnum = -1; + + public String defstr = ""; + public String name; + public double num = -1; + public String str = ""; + public PropertyType type; + + + /** + * Property + * + * @param key key + * @param default_value default value + * @param entry_type type + * @param entry_comment entry comment + */ + public Property(String key, boolean default_value, PropertyType entry_type, String entry_comment) { + name = key; + defbool = default_value; + type = entry_type; + entryComment = entry_comment; + } + + + /** + * Property entry + * + * @param key property key + * @param default_value default value + * @param entry_type property type from enum + * @param entry_comment property comment or null + */ + public Property(String key, double default_value, PropertyType entry_type, String entry_comment) { + name = key; + defnum = default_value; + type = entry_type; + entryComment = entry_comment; + } + + + /** + * Property + * + * @param key key + * @param default_value default value + * @param entry_type type + * @param entry_comment entry comment + */ + public Property(String key, String default_value, PropertyType entry_type, String entry_comment) { + name = key; + defstr = default_value; + type = entry_type; + entryComment = entry_comment; + } + + + /** + * Get boolean + * + * @return the boolean + */ + public boolean getBoolean() + { + return bool; + } + + + /** + * Get number + * + * @return the number + */ + public int getInteger() + { + return (int) Math.round(num); + } + + + /** + * Get number as double + * + * @return the number + */ + public double getDouble() + { + return num; + } + + + /** + * Get string + * + * @return the string + */ + public String getString() + { + return str; + } + + + /** + * is this key pressed? + * + * @return pressed state + */ + public boolean isKeyDown() + { + return type == PropertyType.KEY && Keyboard.isKeyDown((int) num); + } + + + /** + * Is this entry valid? + * + * @return is valid + */ + public boolean isValid() + { + if (type == PropertyType.KEY) { + return Keyboard.getKeyName((int) num) != null; + } + if (type == PropertyType.STRING) { + return str != null; + } + if (type == PropertyType.BOOLEAN || type == PropertyType.INT || type == PropertyType.DOUBLE) { + return true; + } + return false; + } + + + /** + * Load property value from a file + * + * @param string the string loaded + * @return this entry + */ + public boolean parse(String string) + { + if (type == PropertyType.INT) { + if (string == null) { + //Log.finest("* Numeric property \"" + name + "\" not set, setting to default \"" + defnum + "\""); + num = defnum; + return false; + } + try { + num = Integer.parseInt(string.trim()); + } catch (NumberFormatException e) { + //Log.warning("Numeric property \"" + name + "\" has invalid value \"" + string + "\". Falling back to default \"" + defnum + "\""); + num = defnum; + } + } + + if (type == PropertyType.DOUBLE) { + if (string == null) { + //Log.finest("* Numeric property \"" + name + "\" not set, setting to default \"" + defnum + "\""); + num = defnum; + return false; + } + try { + num = Double.parseDouble(string.trim()); + } catch (NumberFormatException e) { + //Log.warning("Numeric property \"" + name + "\" has invalid value \"" + string + "\". Falling back to default \"" + defnum + "\""); + num = defnum; + } + } + + if (type == PropertyType.KEY) { + if (string == null) { + //Log.finest("* Key property \"" + name + "\" not set, setting to default \"" + Keyboard.getKeyName(defnum) + "\""); + num = defnum; + return false; + } + num = Keyboard.getKeyIndex(string); + if (num == Keyboard.KEY_NONE) { + //Log.warning("Key property \"" + name + "\" has invalid value \"" + string + "\". Falling back to default \"" + // + Keyboard.getKeyName(defnum) + "\""); + num = defnum; + } + } + + if (type == PropertyType.STRING) { + if (string == null) { + //Log.finest("* String property \"" + name + "\" not set, setting to default \"" + defstr + "\""); + str = defstr; + return false; + } + this.str = string; + } + + if (type == PropertyType.BOOLEAN) { + if (string == null) { + //Log.finest("* Boolean property \"" + name + "\" not set, setting to default \"" + defbool + "\""); + bool = defbool; + return false; + } + String string2 = string.toLowerCase(); + bool = string2.equals("yes") || string2.equals("true") || string2.equals("on") || string2.equals("enabled") || string2.equals("enable"); + } + + return true; + } + + + /** + * prepare the contents for insertion into Properties + * + * @return the string prepared, or null if type is invalid + */ + @Override + public String toString() + { + if (!isValid()) { + if (type == PropertyType.INT || type == PropertyType.DOUBLE) { + num = defnum; + } + } + if (type == PropertyType.INT) { + return Integer.toString((int) num); + } + if (type == PropertyType.DOUBLE) { + return Calc.floatToString((float) num); + } + if (type == PropertyType.STRING) { + return str; + } + if (type == PropertyType.KEY) { + return Keyboard.getKeyName((int) num) == null ? "none" : Keyboard.getKeyName((int) num); + } + if (type == PropertyType.BOOLEAN) { + return bool ? "True" : "False"; + } + return null; + } + + + /** + * If this entry is not valid, change it to the dafault value. + */ + public void validate() + { + if (!isValid()) { + if (type == PropertyType.KEY) { + //Log.warning("Key property \"" + name + "\" has invalid value (unknown key name). Falling back to default value \"" + // + Keyboard.getKeyName(defnum) + "\""); + num = defnum; + } + if (type == PropertyType.STRING) { + //Log.warning("String property \"" + name + "\" has invalid value (NULL). Falling back to default value \"" + defstr + "\""); + str = defstr; + } + } + } + } + + /** + * Property type enum. + * + * @author MightyPork + * @copy (c) 2012 + */ + private enum PropertyType + { + BOOLEAN, INT, KEY, STRING, DOUBLE; + } + + /** + * Option to put newline before inline comments + */ + private boolean cfgNewlineBeforeComments = true; + /** Disable entry validation */ + private boolean cfgNoValidate = true; + + /** + * Option to put newline between sections.
+ * Sections are detected by text before first dot in identifier. + */ + private boolean cfgSeparateSections = true; + + /** + * Disables enter-leave logging. + */ + private boolean cfgSilent = false; + + private String comment = ""; + + private TreeMap entries; + + private String filename; + + private TreeMap keyRename; + + private PC_SortedProperties pr = new PC_SortedProperties(); + + private TreeMap setValues; + + + /** + * Create property manager from file path and an initial comment. + * + * @param filename file with the props + * @param comment the initial comment. Use \n in it if you want. + */ + public PropertyManager(String filename, String comment) { + this.filename = filename; + this.entries = new TreeMap(); + this.setValues = new TreeMap(); + this.keyRename = new TreeMap(); + this.comment = comment; + } + + + /** + * Load, fix and write to file. + */ + public void apply() + { + if (!cfgSilent) { + //Log.finest("Loading configuration from file \"" + filename + "\""); + } + + boolean needsSave = false; + try { + new File((new File(filename)).getParent()).mkdirs(); + + pr = PropertiesLoader.loadProperties(pr, new FileInputStream(filename)); + + } catch (IOException e) { + needsSave = true; + pr = new PC_SortedProperties(); + } + + pr.cfgSeparateSectionsByEmptyLine = cfgSeparateSections; + pr.cfgEmptyLineBeforeComment = cfgNewlineBeforeComments; + + ArrayList keyList = new ArrayList(); + + // rename keys + for (Entry entry : keyRename.entrySet()) { + if (pr.getProperty(entry.getKey()) == null) { + continue; + } + pr.setProperty(entry.getValue(), pr.getProperty(entry.getKey())); + pr.remove(entry.getKey()); + needsSave = true; + } + + // set the override values into the freshly loaded properties file + for (Entry entry : setValues.entrySet()) { + pr.setProperty(entry.getKey(), entry.getValue()); + needsSave = true; + } + + // validate entries one by one, replace with default when needed + for (Property entry : entries.values()) { + keyList.add(entry.name); + + String propOrig = pr.getProperty(entry.name); + if (!entry.parse(propOrig)) needsSave = true; + if (!cfgNoValidate) { + entry.validate(); + } + + if (entry.entryComment != null) { + pr.setKeyComment(entry.name, entry.entryComment); + } + + if (propOrig == null || !entry.toString().equals(propOrig)) { + pr.setProperty(entry.name, entry.toString()); + + needsSave = true; + } + } + + // removed unused props + for (String propname : pr.keySet().toArray(new String[pr.size()])) { + if (!keyList.contains(propname)) { + pr.remove(propname); + //Log.finest("* Removing unused property \"" + propname + "\" from config file " + filename); + needsSave = true; + } + + } + + // save if needed + if (needsSave) { + try { + //Log.finest("* Saving modified property file " + filename); + pr.store(new FileOutputStream(filename), comment); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + if (!cfgSilent) { + //Log.finest("Configuration loaded."); + } + + setValues.clear(); + keyRename.clear(); + } + + + /** + * Get boolean property + * + * @param n key + * @return the boolean found, or false + */ + public Boolean bool(String n) + { + return getBoolean(n); + } + + + /** + * @param newlineBeforeComments put newline before comments + */ + public void cfgNewlineBeforeComments(boolean newlineBeforeComments) + { + this.cfgNewlineBeforeComments = newlineBeforeComments; + } + + + /** + * @param separateSections do separate sections by newline + */ + public void cfgSeparateSections(boolean separateSections) + { + this.cfgSeparateSections = separateSections; + } + + + /** + * @param silent the cfgSilent to set + */ + public void cfgSilent(boolean silent) + { + this.cfgSilent = silent; + } + + + /** + * @param validate enable validation + */ + public void enableValidation(boolean validate) + { + this.cfgNoValidate = !validate; + } + + + /** + * Get boolean property + * + * @param n key + * @return the boolean found, or false + */ + public Boolean flag(String n) + { + return getBoolean(n); + } + + + /** + * Get a property entry (rarely used) + * + * @param n key + * @return the entry + */ + private Property get(String n) + { + try { + return entries.get(n); + } catch (Throwable t) { + return null; + } + } + + + /** + * Get boolean property + * + * @param n key + * @return the boolean found, or false + */ + public Boolean getBoolean(String n) + { + try { + return entries.get(n).getBoolean(); + } catch (Throwable t) { + return false; + } + } + + + /** + * Get numeric property + * + * @param n key + * @return the int found, or null + */ + public Integer getInt(String n) + { + return getInteger(n); + } + + + /** + * Get numeric property + * + * @param n key + * @return the int found, or null + */ + public Integer getInteger(String n) + { + try { + return get(n).getInteger(); + } catch (Throwable t) { + return -1; + } + } + + + /** + * Get numeric property as double + * + * @param n key + * @return the double found, or null + */ + public Double getDouble(String n) + { + try { + return get(n).getDouble(); + } catch (Throwable t) { + return -1D; + } + } + + + /** + * Get numeric property + * + * @param n key + * @return the int found, or null + */ + public Integer getNum(String n) + { + return getInteger(n); + } + + + /** + * Get string property + * + * @param n key + * @return the string found, or null + */ + public String getString(String n) + { + try { + return get(n).getString(); + } catch (Throwable t) { + return null; + } + } + + + /** + * Get numeric property + * + * @param n key + * @return the int found, or null + */ + public Integer integer(String n) + { + try { + return get(n).getInteger(); + } catch (Throwable t) { + return -1; + } + } + + + /** + * Is the key pressed? (works only for properties of type KEY) + * + * @param n key of the key property + * @return is pressed + */ + public Boolean isKeyDown(String n) + { + try { + return entries.get(n).isKeyDown(); + } catch (Throwable t) { + return false; + } + } + + + /** + * Get numeric property + * + * @param n key + * @return the int found, or null + */ + public Integer num(String n) + { + return getInteger(n); + } + + + /** + * Add a boolean property + * + * @param n key + * @param d default value + */ + public void putBoolean(String n, boolean d) + { + entries.put(n, new Property(n, d, PropertyType.BOOLEAN, null)); + return; + } + + + /** + * Add a boolean property + * + * @param n key + * @param d default value + * @param comment the in-file comment + */ + public void putBoolean(String n, boolean d, String comment) + { + entries.put(n, new Property(n, d, PropertyType.BOOLEAN, comment)); + return; + } + + + /** + * Add a numeric property (double) + * + * @param n key + * @param d default value + */ + public void putDouble(String n, int d) + { + entries.put(n, new Property(n, d, PropertyType.DOUBLE, null)); + return; + } + + + /** + * Add a numeric property (double) + * + * @param n key + * @param d default value + * @param comment the in-file comment + */ + public void putDouble(String n, int d, String comment) + { + entries.put(n, new Property(n, d, PropertyType.DOUBLE, comment)); + return; + } + + + /** + * Add a numeric property + * + * @param n key + * @param d default value + */ + public void putInteger(String n, int d) + { + entries.put(n, new Property(n, d, PropertyType.INT, null)); + return; + } + + + /** + * Add a numeric property + * + * @param n key + * @param d default value + * @param comment the in-file comment + */ + public void putInteger(String n, int d, String comment) + { + entries.put(n, new Property(n, d, PropertyType.INT, comment)); + return; + } + + + /** + * Add a numeric property + * + * @param n key + * @param d default value + */ + public void putKey(String n, int d) + { + entries.put(n, new Property(n, d, PropertyType.KEY, null)); + return; + } + + + /** + * Add a numeric property + * + * @param n key + * @param d default value + * @param comment the in-file comment + */ + public void putKey(String n, int d, String comment) + { + entries.put(n, new Property(n, d, PropertyType.KEY, comment)); + return; + } + + + /** + * Add a string property + * + * @param n key + * @param d default value + */ + public void putString(String n, String d) + { + entries.put(n, new Property(n, d, PropertyType.STRING, null)); + return; + } + + + /** + * Add a string property + * + * @param n key + * @param d default value + * @param comment the in-file comment + */ + public void putString(String n, String d, String comment) + { + entries.put(n, new Property(n, d, PropertyType.STRING, comment)); + return; + } + + + /** + * Rename key before doing "apply"; value is preserved + * + * @param oldKey old key + * @param newKey new key + */ + public void renameKey(String oldKey, String newKey) + { + keyRename.put(oldKey, newKey); + return; + } + + + /** + * Set value saved to certain key; use to save runtime-changed configuration + * values. + * + * @param key key + * @param value the saved value + */ + public void setValue(String key, Object value) + { + setValues.put(key, value.toString()); + return; + } + + + /** + * Get string property + * + * @param n key + * @return the string found, or null + */ + public String str(String n) + { + try { + return get(n).getString(); + } catch (Throwable t) { + return null; + } + } + + + /** + * Get string property + * + * @param n key + * @return the string found, or null + */ + public String string(String n) + { + try { + return get(n).getString(); + } catch (Throwable t) { + return null; + } + } +} diff --git a/src/com/porcupine/util/StringUtils.java b/src/com/porcupine/util/StringUtils.java new file mode 100644 index 0000000..15d6a69 --- /dev/null +++ b/src/com/porcupine/util/StringUtils.java @@ -0,0 +1,114 @@ +package com.porcupine.util; + + +/** + * General purpose string utilities + * + * @author MightyPork + */ +public class StringUtils { + + /** + * Get if string is in array + * + * @param needle checked string + * @param case_sensitive case sensitive comparision + * @param haystack array of possible values + * @return is in array + */ + public static boolean isInArray(String needle, boolean case_sensitive, String... haystack) + { + if (case_sensitive) { + for (String s : haystack) { + if (needle.equals(s)) return true; + } + return false; + } else { + for (String s : haystack) { + if (needle.equalsIgnoreCase(s)) return true; + } + return false; + } + } + + + /** + * Repeat a string + * + * @param repeated string + * @param count + * @return output + */ + public static String repeat(String repeated, int count) + { + String s = ""; + for (int i = 0; i < count; i++) + s += repeated; + return s; + } + + + /** + * convert string to a same-length sequence of # marks + * + * @param password password + * @return encoded + */ + public static String passwordify(String password) + { + return passwordify(password, "#"); + } + + + /** + * convert string to a same-length sequence of chars + * + * @param password password + * @param replacing character used in output + * @return encoded + */ + public static String passwordify(String password, String replacing) + { + return repeat(replacing, password.length()); + } + + + /** + * Get ordinal version of numbers (1 = 1st, 5 = 5th etc.) + * + * @param number number + * @return ordinal, string + */ + public static String numberToOrdinal(int number) + { + if (number % 100 < 4 || number % 100 > 13) { + if (number % 10 == 1) return number + "st"; + if (number % 10 == 2) return number + "nd"; + if (number % 10 == 3) return number + "rd"; + } + return number + "th"; + } + + + /** + * Format number with thousands separated by a dot. + * + * @param number number + * @return string 12.004.225 + */ + public static String formatInt(long number) + { + String num = number + ""; + String out = ""; + String dot = "."; + int cnt = 1; + for (int i = num.length() - 1; i >= 0; i--) { + out = num.charAt(i) + out; + if (cnt % 3 == 0 && i > 0) out = dot + out; + cnt++; + } + + return out; + } + +} diff --git a/src/com/porcupine/util/VarargsParser.java b/src/com/porcupine/util/VarargsParser.java new file mode 100644 index 0000000..de50722 --- /dev/null +++ b/src/com/porcupine/util/VarargsParser.java @@ -0,0 +1,54 @@ +package com.porcupine.util; + + +import java.util.LinkedHashMap; + + +/** + * Varargs parser
+ * Converts an array of repeated "key, value" pairs to a LinkedHashMap.
+ * example: + * + *
+ * 
+ * Object[] array = { "one", 1, "two", 4, "three", 9, "four", 16 };
+ * Map<String, Integer> args = new VarargsParser<String, Integer>().parse(array);
+ * 
+ * + * @author MightyPork + * @param Type for Map keys + * @param Type for Map values + */ +public class VarargsParser { + + /** + * Parse array of vararg key, value pairs to a LinkedHashMap. + * + * @param args varargs + * @return LinkedHashMap + * @throws ClassCastException in case of incompatible type in the array + * @throws IllegalArgumentException in case of invalid array length (odd) + */ + @SuppressWarnings("unchecked") + public LinkedHashMap parse(Object... args) throws ClassCastException, IllegalArgumentException + { + LinkedHashMap attrs = new LinkedHashMap(); + + if (args.length % 2 != 0) { + throw new IllegalArgumentException("Odd number of elements in varargs map!"); + } + + K key = null; + for (Object o : args) { + if (key == null) { + if (o == null) throw new RuntimeException("Key cannot be NULL in varargs map."); + key = (K) o; + } else { + attrs.put(key, (V) o); + key = null; + } + } + + return attrs; + } +} diff --git a/src/net/tortuga/App.java b/src/net/tortuga/App.java new file mode 100644 index 0000000..1d0f9fa --- /dev/null +++ b/src/net/tortuga/App.java @@ -0,0 +1,621 @@ +package net.tortuga; + + +import static org.lwjgl.opengl.GL11.*; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Insets; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.channels.FileLock; +import java.util.Calendar; +import java.util.Random; + +import javax.swing.*; + +import net.tortuga.animations.EmptyAnimator; +import net.tortuga.animations.IRenderer; +import net.tortuga.animations.LeavesAnimator; +import net.tortuga.animations.RainAnimator; +import net.tortuga.animations.SnowAnimator; +import net.tortuga.gui.screens.Screen; +import net.tortuga.gui.screens.ScreenMenuMain; +import net.tortuga.gui.screens.ScreenSplash; +import net.tortuga.input.Keys; +import net.tortuga.sounds.Effects; +import net.tortuga.sounds.SoundManager; +import net.tortuga.threads.ThreadSaveScreenshot; +import net.tortuga.util.Log; +import net.tortuga.util.Utils; + +import org.lwjgl.BufferUtils; +import org.lwjgl.LWJGLException; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.openal.AL; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.DisplayMode; +import org.lwjgl.opengl.PixelFormat; + +import com.porcupine.coord.Coord; +import com.porcupine.time.TimerDelta; +import com.porcupine.time.TimerInterpolating; +import com.porcupine.util.FileUtils; + + +/** + * SECTOR main class + * + * @author MightyPork + */ +public class App { + + /** instance */ + public static App inst; + /** Current delta time (secs since last render) */ + public static double currentDelta = 0; + + private static DisplayMode windowDisplayMode = null; + + /** Ambient effect renderer */ + public static IRenderer weatherAnimation; + + /** current screen */ + public static Screen screen = null; + + /** Flag that screenshot is scheduled to be taken next loop */ + public static boolean scheduledScreenshot = false; + + /** Current weather animation */ + public static EWeather weather; + + + private static boolean lockInstance() + { + final File lockFile = new File(Utils.getGameFolder(), ".lock"); + try { + final RandomAccessFile randomAccessFile = new RandomAccessFile(lockFile, "rw"); + final FileLock fileLock = randomAccessFile.getChannel().tryLock(); + if (fileLock != null) { + Runtime.getRuntime().addShutdownHook(new Thread() { + + @Override + public void run() + { + try { + fileLock.release(); + randomAccessFile.close(); + lockFile.delete(); + } catch (Exception e) { + System.out.println("Unable to remove lock file."); + e.printStackTrace(); + } + } + }); + return true; + } + } catch (Exception e) { + System.out.println("Unable to create and/or lock file."); + e.printStackTrace(); + } + return false; + } + + + /** + * Is if FS + * + * @return is in fs + */ + public static boolean isFullscreen() + { + return Display.isFullscreen(); + } + + + /** + * @param args + */ + public static void main(String[] args) + { + inst = new App(); + try { + inst.start(); + } catch (Throwable t) { + showCrashReport(t); + } + + } + + + /** + * Show crash report dialog with error stack trace. + * + * @param error + */ + public static void showCrashReport(Throwable error) + { + Log.e(error); + + try { + inst.deinit(); + } catch (Throwable t) {} + + JFrame f = new JFrame("Tortuga has crashed!"); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + f.getContentPane().setLayout(new BoxLayout(f.getContentPane(), BoxLayout.Y_AXIS)); + + StringWriter sw = new StringWriter(); + error.printStackTrace(new PrintWriter(sw)); + String exceptionAsString = sw.toString(); + + String errorLogAsString = "Not found."; + String wholeLogAsString = "Not found."; + + try { + wholeLogAsString = FileUtils.fileToString(Utils.getGameSubfolder(Constants.FILE_LOG)); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + errorLogAsString = FileUtils.fileToString(Utils.getGameSubfolder(Constants.FILE_LOG_E)); + } catch (Exception e) { + e.printStackTrace(); + } + + String txt = ""; + txt = ""; + txt += "TORTUGA GAME HAS CRASHED!\n"; + txt += "\n"; + txt += "Please report it to MightyPork:\n"; + txt += "\tE-mail: ondra@ondrovo.com\n"; + txt += "\tTwitter: #MightyPork (post log via pastebin.com)\n"; + txt += "\n"; + txt += "\n"; + txt += "Version: " + Constants.VERSION_NAME + "\n"; + txt += "\n"; + txt += "\n"; + txt += "### STACK TRACE ###\n"; + txt += "\n"; + txt += exceptionAsString + "\n"; + txt += "\n"; + txt += "\n"; + txt += "### ERROR LOG ###\n"; + txt += "\n"; + txt += errorLogAsString + "\n"; + txt += "\n"; + txt += "\n"; + txt += "### FULL LOG ###\n"; + txt += "\n"; + txt += wholeLogAsString + "\n"; + + // Create Scrolling Text Area in Swing + JTextArea ta = new JTextArea(txt, 20, 70); + ta.setFont(new Font("Courier", 0, 16)); + ta.setMargin(new Insets(10, 10, 10, 10)); + ta.setEditable(false); + ta.setLineWrap(false); + JScrollPane sbrText = new JScrollPane(ta); + sbrText.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10), BorderFactory.createEtchedBorder())); + sbrText.setWheelScrollingEnabled(true); + sbrText.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + sbrText.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + + // Create Quit Button + JButton btnQuit = new JButton("Quit"); + btnQuit.setAlignmentX(Component.CENTER_ALIGNMENT); + + btnQuit.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) + { + System.exit(0); + } + }); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + buttonPane.add(btnQuit); + + f.getContentPane().add(sbrText); + f.getContentPane().add(buttonPane); + + // Close when the close button is clicked + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + //Display Frame + f.pack(); // Adjusts frame to size of components + + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + f.setLocation((dim.width - f.getWidth()) / 2, (dim.height - f.getHeight()) / 2); + + f.setVisible(true); + + while (true) {} + } + + + private void deinit() + { + Display.destroy(); + Mouse.destroy(); + Keyboard.destroy(); + SoundManager.player.clear(); + AL.destroy(); + } + + + /** + * Quit to OS + */ + public void exit() + { + deinit(); + System.exit(0); + } + + + /** + * Get current screen + * + * @return screen + */ + public Screen getScreen() + { + return screen; + } + + + /** + * Get screen size + * + * @return size + */ + public Coord getSize() + { + return new Coord(Display.getWidth(), Display.getHeight()); + } + + +// INIT + + private void init() throws LWJGLException + { + GameConfig.initLoad(); + + Log.enable(GameConfig.logEnabled); + Log.setPrintToStdout(GameConfig.logStdOut); + + Log.i("Game version: " + Constants.VERSION_NAME); + + // init display + Display.setDisplayMode(windowDisplayMode = new DisplayMode(Constants.WINDOW_SIZE_X, Constants.WINDOW_SIZE_Y)); + Display.setResizable(GameConfig.enableResize); + Display.setVSyncEnabled(GameConfig.enableVsync); + Display.setTitle(Constants.TITLEBAR); + + int samples = GameConfig.antialiasing; + while (true) { + try { + Display.create(new PixelFormat().withSamples(samples).withAlphaBits(4)); + Log.i("Created display with " + samples + "x multisampling."); + break; + } catch (LWJGLException e) { + Log.w("Failed to create display with " + samples + "x multisampling, trying " + samples / 2 + "x."); + if (samples >= 2) { + samples /= 2; + } else if (samples == 1) { + samples = 0; + } else if (samples == 0) { + Log.e("Could not create display.", e); + exit(); + } + } + } + + SoundManager.player.setMaxSources(256); + SoundManager.player.init(); + SoundManager.setListener(Constants.LISTENER_POS); + + // TODO from config + SoundManager.volumeGui.set(1f); + SoundManager.volumeEffects.set(1f); + SoundManager.volumeWater.set(1f); + SoundManager.volumeAmbients.set(1f); + + Mouse.create(); + Keyboard.create(); + Keyboard.enableRepeatEvents(false); + Keys.init(); + + LoadingManager.loadForSplash(); + + StaticInitializer.initOnStartup(); + + //Display.update(); + if (GameConfig.startInFullscreen) { + switchFullscreen(); + Display.update(); + } + + new ThreadScreenshotTrigger().start(); + + Random rand = new Random(); + + if (rand.nextInt(3) == 0) { + // chance to get no weather effect + weatherAnimation = new EmptyAnimator(); + weather = EWeather.NONE; + } else { + // get weather based on month + Calendar cal = Calendar.getInstance(); + int month = cal.get(Calendar.MONTH); + + int negMonth = month + 6; + if (negMonth >= 12) negMonth -= 12; + + int auMonth = month - 4; + if (auMonth < 0) auMonth += 12; + + int suMonth = month + 2; + if (suMonth < 0) suMonth += 12; + + double snowChance = (0.4 + rand.nextDouble()) * (6 - Math.abs(negMonth - 6)) * 1.5; + double rainChance = (0.3 + rand.nextDouble()) * (6 - Math.abs(suMonth - 6)) * 1.5; + double leafChance = (0.3 + rand.nextDouble()) * (6 - Math.abs(auMonth - 6)) * 1.4; + + Log.f3("Weather chance:"); + Log.f3("SNOW = " + snowChance); + Log.f3("RAIN = " + rainChance); + Log.f3("LEAF = " + leafChance); + + if (snowChance > leafChance && snowChance >= rainChance) { + weatherAnimation = new SnowAnimator(); + weather = EWeather.SNOW; + + } else if (rainChance >= snowChance && rainChance > leafChance) { + weatherAnimation = new RainAnimator(); + weather = EWeather.RAIN; + + } else if (leafChance > rainChance && leafChance >= snowChance) { + weatherAnimation = new LeavesAnimator(); + weather = EWeather.LEAVES; + + } else { + weatherAnimation = new RainAnimator(); + weather = EWeather.RAIN; + + } + } + } + + + private void start() throws LWJGLException + { + if (!lockInstance()) { + System.out.println("No more than 1 instance of this game can be running at a time."); + + JOptionPane.showMessageDialog(null, "The game is already running.", "Instance error", JOptionPane.ERROR_MESSAGE); + + exit(); + return; + } + + Log.enable(true); + Log.setPrintToStdout(true); + + init(); + mainLoop(); + deinit(); + } + +// INIT END + +// UPDATE LOOP + + /** timer */ + private TimerDelta timerRender; + private TimerInterpolating timerGui; + + private int timerAfterResize = 0; + + + private void mainLoop() + { + screen = new ScreenSplash(); + + screen.init(); + + timerRender = new TimerDelta(); + timerGui = new TimerInterpolating(Constants.FPS_GUI); + + while (!Display.isCloseRequested()) { + glLoadIdentity(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + timerGui.sync(); + + int ticks = timerGui.getSkipped(); + + if (ticks >= 1) { + screen.updateGui(); + timerGui.startNewFrame(); + } + + double delta = timerRender.getDelta(); + + currentDelta = delta; + + // RENDER + screen.render(delta); + SoundManager.update(delta); + + Display.update(); + + if (scheduledScreenshot) { + takeScreenshot(); + scheduledScreenshot = false; + } + + if (Keys.justPressed(Keyboard.KEY_F11)) { + Log.f2("F11, toggle fullscreen."); + switchFullscreen(); + screen.onFullscreenChange(); + Keys.destroyChangeState(Keyboard.KEY_F11); + weatherAnimation.onFullscreenChange(); + } + + if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) { + if (Keyboard.isKeyDown(Keyboard.KEY_Q)) { + Log.f2("Ctrl+Q, force quit."); + Keys.destroyChangeState(Keyboard.KEY_Q); + exit(); + return; + } + + if (Keyboard.isKeyDown(Keyboard.KEY_M)) { + Log.f2("Ctrl+M, go to main menu."); + Keys.destroyChangeState(Keyboard.KEY_M); + screen.rootPanel.onClose(); + screen.rootPanel.onBlur(); + replaceScreen(new ScreenMenuMain()); + } + + if (Keyboard.isKeyDown(Keyboard.KEY_F)) { + Log.f2("Ctrl+F, switch fullscreen."); + Keys.destroyChangeState(Keyboard.KEY_F); + switchFullscreen(); + screen.onFullscreenChange(); + weatherAnimation.onFullscreenChange(); + } + } + + if (Display.wasResized()) { + screen.onWindowResize(); + timerAfterResize = 0; + } else { + timerAfterResize++; + if (timerAfterResize > Constants.FPS_GUI * 0.3) { + timerAfterResize = 0; + int x = Display.getX(); + int y = Display.getY(); + + int w = Display.getWidth(); + int h = Display.getHeight(); + if (w % 2 != 0 || h % 2 != 0) { + try { + Display.setDisplayMode(windowDisplayMode = new DisplayMode(w - w % 2, h - h % 2)); + screen.onWindowResize(); + Display.setLocation(x, y); + } catch (LWJGLException e) { + e.printStackTrace(); + } + } + } + } + + try { + Display.sync(Constants.FPS_RENDER); + } catch (Throwable t) { + Log.e("Your graphics card driver does not support fullscreen properly.", t); + + try { + Display.setDisplayMode(windowDisplayMode); + } catch (LWJGLException e) { + Log.e("Error going back from corrupted fullscreen."); + showCrashReport(e); + } + } + } + } + + +// UPDATE LOOP END + + /** + * Do take a screenshot + */ + public void takeScreenshot() + { + Effects.play("gui.screenshot"); + + glReadBuffer(GL_FRONT); + int width = Display.getDisplayMode().getWidth(); + int height = Display.getDisplayMode().getHeight(); + int bpp = 4; // Assuming a 32-bit display with a byte each for red, green, blue, and alpha. + ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bpp); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + + new ThreadSaveScreenshot(buffer, width, height, bpp).start(); + } + + + /** + * Replace screen + * + * @param newScreen new screen + */ + public void replaceScreen(Screen newScreen) + { + screen = newScreen; + screen.init(); + } + + + /** + * Replace screen, don't init it + * + * @param newScreen new screen + */ + public void replaceScreenNoInit(Screen newScreen) + { + screen = newScreen; + } + + + /** + * Toggle FS if possible + */ + public void switchFullscreen() + { + try { + if (!Display.isFullscreen()) { + Log.f3("Entering fullscreen."); + // save window resize + windowDisplayMode = new DisplayMode(Display.getWidth(), Display.getHeight()); + + Display.setDisplayMode(Display.getDesktopDisplayMode()); + Display.setFullscreen(true); + Display.update(); +// +// +// DisplayMode mode = Display.getDesktopDisplayMode(); //findDisplayMode(WIDTH, HEIGHT); +// Display.setDisplayModeAndFullscreen(mode); + } else { + Log.f3("Leaving fullscreen."); + Display.setDisplayMode(windowDisplayMode); + Display.update(); + } + } catch (Throwable t) { + Log.e("Failed to toggle fullscreen mode.", t); + try { + Display.setDisplayMode(windowDisplayMode); + Display.update(); + } catch (Throwable t1) { + showCrashReport(t1); + } + } + } +} diff --git a/src/net/tortuga/Constants.java b/src/net/tortuga/Constants.java new file mode 100644 index 0000000..2b0c185 --- /dev/null +++ b/src/net/tortuga/Constants.java @@ -0,0 +1,47 @@ +package net.tortuga; + + +import com.porcupine.coord.Coord; + + +/** + * Sector constants + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class Constants { + + // STRINGS + public static final int VERSION_NUMBER = 1; + public static final String VERSION_NAME = "1.0"; + public static final String TITLEBAR = "Tortuga " + VERSION_NAME + " - MightyPork's programming game"; + + // FILES+DIRS + public static final String APP_DIR = "tortuga"; + + public static final String DIR_SCREENSHOTS = "screenshots"; + + public static final String FILE_CONFIG = "config.ini"; + public static final String FILE_LOG = "Runtime.log"; + public static final String FILE_LOG_E = "Error.log"; + + // AUDIO + public static final Coord LISTENER_POS = new Coord(0, 0, 0); + + // TIMING + public static final int FPS_GUI = 40; + //public static final double SPEED_MUL = (40D / FPS_GUI); + + public static final int FPS_RENDER = 200; // max + + // LOGGING GROUPS + public static final boolean LOG_FONTS = false; + public static final boolean LOG_TEXTURES = false; + public static final boolean LOG_SOUNDS = true; + public static final boolean LOG_XML_LOADING = false; + + // INITIAL WINDOW SIZE (later loaded from config file) + public static final int WINDOW_SIZE_X = 1024; + public static final int WINDOW_SIZE_Y = 768; +} diff --git a/src/net/tortuga/CustomIonMarks.java b/src/net/tortuga/CustomIonMarks.java new file mode 100644 index 0000000..054fc82 --- /dev/null +++ b/src/net/tortuga/CustomIonMarks.java @@ -0,0 +1,59 @@ +package net.tortuga; + + +import net.tortuga.level.LevelPars; +import net.tortuga.level.map.EntityDescriptorList; +import net.tortuga.level.map.TileGridDescriptor; +import net.tortuga.level.map.TurtleMapDescriptor; +import net.tortuga.level.map.entities.EntityDescriptor; +import net.tortuga.level.map.entities.TurtleDescriptor; +import net.tortuga.level.program.GrainList; +import net.tortuga.level.program.StoneList; +import net.tortuga.util.IonCoordI; + +import com.porcupine.ion.Ion; + + +/** + * Class adding ION marks for custom ionizable objects + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class CustomIonMarks { + + // low level things + public static final byte COORD = 50; + + // level, map, turtle, entities + public static final byte MAP_DESCRIPTOR = 100; + public static final byte TURTLE_DESCRIPTOR = 101; + public static final byte ENTITY_DESCRIPTOR = 102; + public static final byte TILE_LIST = 103; + public static final byte ENTITY_LIST = 104; + public static final byte PGM_GRAIN_LIST = 105; + public static final byte PGM_STONE_LIST = 106; + public static final byte LEVEL_PARS = 107; + + +// public static final byte LEVEL = 105; +// +// public static final byte SAVED_PROGRAM = 106; + + /** + * Register ion marks + */ + public static void init() + { + Ion.registerIonizable(COORD, IonCoordI.class); + Ion.registerIonizable(MAP_DESCRIPTOR, TurtleMapDescriptor.class); + Ion.registerIonizable(TURTLE_DESCRIPTOR, TurtleDescriptor.class); + Ion.registerIonizable(ENTITY_DESCRIPTOR, EntityDescriptor.class); + Ion.registerIonizable(TILE_LIST, TileGridDescriptor.class); + Ion.registerIonizable(ENTITY_LIST, EntityDescriptorList.class); + Ion.registerIonizable(PGM_GRAIN_LIST, GrainList.class); + Ion.registerIonizable(PGM_STONE_LIST, StoneList.class); + Ion.registerIonizable(LEVEL_PARS, LevelPars.class); + } + +} diff --git a/src/net/tortuga/EWeather.java b/src/net/tortuga/EWeather.java new file mode 100644 index 0000000..c0c2ef1 --- /dev/null +++ b/src/net/tortuga/EWeather.java @@ -0,0 +1,7 @@ +package net.tortuga; + + +public enum EWeather +{ + RAIN, SNOW, LEAVES, NONE; +} diff --git a/src/net/tortuga/GameConfig.java b/src/net/tortuga/GameConfig.java new file mode 100644 index 0000000..fd8637d --- /dev/null +++ b/src/net/tortuga/GameConfig.java @@ -0,0 +1,113 @@ +package net.tortuga; + + +import net.tortuga.util.Utils; + +import com.porcupine.util.PropertyManager; + + +/** + * Configuration + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class GameConfig { + + public static final String pk_win_resize = "window.resizable"; + public static final String pk_win_fs = "window.start.fullscreen"; + + public static final String pk_antialiasing = "graphics.antialiasing"; + public static final String pk_vsync = "graphics.vsync"; + + public static final String pk_in_mouse = "input.mouse.sensitivity"; + + public static final String pk_sound_volume = "audio.volume.sound"; + public static final String pk_music_volume = "audio.volume.music"; + + public static final String pk_log = "log.enable"; + public static final String pk_log_stdout = "log.toStdOut"; + + public static final String pk_key_shield = "key.shield"; + + public static boolean startInFullscreen; + public static boolean enableVsync; + public static boolean enableResize; + public static int mouseSensitivity; + public static int audioVolumeSound; + public static int audioVolumeMusic; + public static int antialiasing; + public static boolean logEnabled; + public static boolean logStdOut; + + private static PropertyManager p; + + + /** + * Init property manager and load from file. + */ + public static void initLoad() + { + p = new PropertyManager(Utils.getGameFolder() + "/" + Constants.FILE_CONFIG, "Main Tortuga configuration file."); + + p.cfgSeparateSections(true); + p.cfgNewlineBeforeComments(false); + + p.putBoolean(pk_win_fs, false, "Start in fullscreen."); + p.putInteger(pk_antialiasing, 0, "Antialiasing 0, 2, 4, 8; Depends of graphic card."); + p.putBoolean(pk_vsync, true, "Enable vsync"); + p.putBoolean(pk_win_resize, true, "Make window resizable"); + + p.putInteger(pk_in_mouse, 1000, "Mouse sensitivity for ship movement. 1000 is the default sensitivity."); + + p.putInteger(pk_sound_volume, 100, "Sound volume, 0-100."); + p.putInteger(pk_music_volume, 100, "Music volume, 0-100."); + + p.putBoolean(pk_log, true, "Enable logging."); + p.putBoolean(pk_log_stdout, false, "Print log messages also to stdout."); + + saveLoad(); + useLoaded(); + } + + + /** + * Set property value (call saveLoad and useLoaded afterwards) + * + * @param key key to set + * @param newValue new value assigned + */ + public static void setNewProp(String key, Object newValue) + { + p.setValue(key, newValue); + } + + + /** + * Save changes and load from file. + */ + public static void saveLoad() + { + p.apply(); + } + + + /** + * Store loaded config to fields + */ + public static void useLoaded() + { + startInFullscreen = p.getBoolean(pk_win_fs); + enableVsync = p.getBoolean(pk_vsync); + enableResize = p.getBoolean(pk_win_resize); + + mouseSensitivity = p.getInteger(pk_in_mouse); + + antialiasing = p.getInteger(pk_antialiasing); + audioVolumeSound = p.getInteger(pk_sound_volume); + audioVolumeMusic = p.getInteger(pk_music_volume); + + logEnabled = p.getBoolean(pk_log); + logStdOut = p.getBoolean(pk_log_stdout); + } +} diff --git a/src/net/tortuga/LoadingManager.java b/src/net/tortuga/LoadingManager.java new file mode 100644 index 0000000..cebbc98 --- /dev/null +++ b/src/net/tortuga/LoadingManager.java @@ -0,0 +1,126 @@ +package net.tortuga; + + +import net.tortuga.fonts.Fonts; +import net.tortuga.sounds.SoundManager; +import net.tortuga.textures.Textures; +import net.tortuga.util.Log; + + +/** + * Class responsible for resource loading. + * + * @author MightyPork + */ +public class LoadingManager { + + private static final int groups = 3; + + private static int lastloaded = -1; + private static long beginTime; + + + private static void timerStart() + { + beginTime = System.currentTimeMillis(); + } + + + private static double timerGet() + { + return (System.currentTimeMillis() - beginTime) / 1000D; + } + + + /** + * Load resources needed to animate splash + */ + public static void loadForSplash() + { + Log.f1("Loading resources needed for Splash screen."); + + timerStart(); + Fonts.loadForSplash(); + Log.i("LOADING: Fonts for Splash loaded in " + timerGet() + "s"); + + timerStart(); + SoundManager.loadForSplash(); + Log.i("LOADING: Sounds for Splash loaded in " + timerGet() + "s"); + + timerStart(); + Textures.loadForSplash(); + Log.i("LOADING: Textures for Splash loaded in " + timerGet() + "s"); + } + + + /** + * Get info text for resource group (eg. Loading sounds...) + * + * @return text + */ + public static String getSplashInfo() + { + switch (lastloaded + 1) { + case 0: + return "Loading fonts..."; + case 1: + return "Loading textures..."; + case 2: + return "Loading models..."; + case 3: + return "Loading sounds..."; + } + return "Loading..."; + } + + + /** + * Load next resource group + */ + public static void loadGroup() + { + switch (lastloaded + 1) { + case 0: + timerStart(); + Fonts.load(); + Log.i("LOADING: Fonts loaded in " + timerGet() + "s"); + break; + + case 1: + timerStart(); + Textures.load(); + Log.i("LOADING: Textures loaded in " + timerGet() + "s"); + break; + + case 2: + timerStart(); + SoundManager.load(); + Log.i("LOADING: Sounds loaded in " + timerGet() + "s"); + break; + } + + lastloaded++; + } + + + /** + * Check if has more resource groups to load + * + * @return has more + */ + public static boolean hasMoreGroups() + { + return lastloaded < groups; + } + + + /** + * Called after all resources have been loaded. + */ + public static void onResourcesLoaded() + { + Log.i("LOADING: All resources loaded."); + + StaticInitializer.initPostLoad(); + } +} diff --git a/src/net/tortuga/StaticInitializer.java b/src/net/tortuga/StaticInitializer.java new file mode 100644 index 0000000..ac392f9 --- /dev/null +++ b/src/net/tortuga/StaticInitializer.java @@ -0,0 +1,41 @@ +package net.tortuga; + + +import net.tortuga.gui.widgets.ColorScheme; +import net.tortuga.level.map.entities.EntityRegistry; +import net.tortuga.level.map.tiles.MapTileRegistry; +import net.tortuga.level.program.GrainRegistry; +import net.tortuga.level.program.StoneRegistry; + + +/** + * Initialization utility, initializing all the static stuff that is needed + * before starting main loop. + * + * @author MightyPork + */ +public class StaticInitializer { + + /** + * Init static things and start threads.
+ * This is called on startup, even before the splash screen. + */ + public static void initOnStartup() + { + CustomIonMarks.init(); + + ColorScheme.init(); + GrainRegistry.init(); + StoneRegistry.init(); + MapTileRegistry.init(); + EntityRegistry.init(); + } + + + /** + * Initialize all. + */ + public static void initPostLoad() + {} + +} diff --git a/src/net/tortuga/ThreadScreenshotTrigger.java b/src/net/tortuga/ThreadScreenshotTrigger.java new file mode 100644 index 0000000..7bfdbce --- /dev/null +++ b/src/net/tortuga/ThreadScreenshotTrigger.java @@ -0,0 +1,31 @@ +package net.tortuga; + + +import net.tortuga.input.Keys; +import net.tortuga.util.Log; + +import org.lwjgl.input.Keyboard; + + +/** + * @author MightyPork + */ +public class ThreadScreenshotTrigger extends Thread { + + @Override + public void run() + { + while (true) { + if (Keys.justPressed(Keyboard.KEY_F2)) { + Log.f2("F2, taking screenshot."); + App.scheduledScreenshot = true; + Keys.destroyChangeState(Keyboard.KEY_F2); + } + try { + sleep(2); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/net/tortuga/animations/EmptyAnimator.java b/src/net/tortuga/animations/EmptyAnimator.java new file mode 100644 index 0000000..d896af7 --- /dev/null +++ b/src/net/tortuga/animations/EmptyAnimator.java @@ -0,0 +1,30 @@ +package net.tortuga.animations; + + +/** + * Empty animation (no effect) + * + * @author MightyPork + */ +public class EmptyAnimator implements IRenderer { + + /** + * New empty animation + */ + public EmptyAnimator() {} + + + @Override + public void updateGui() + {} + + + @Override + public void render(double delta) + {} + + + @Override + public void onFullscreenChange() + {} +} diff --git a/src/net/tortuga/animations/IRenderer.java b/src/net/tortuga/animations/IRenderer.java new file mode 100644 index 0000000..d9deeac --- /dev/null +++ b/src/net/tortuga/animations/IRenderer.java @@ -0,0 +1,13 @@ +package net.tortuga.animations; + + +public interface IRenderer { + + public void updateGui(); + + + public void render(double delta); + + + public void onFullscreenChange(); +} diff --git a/src/net/tortuga/animations/LeavesAnimator.java b/src/net/tortuga/animations/LeavesAnimator.java new file mode 100644 index 0000000..ec9d9ef --- /dev/null +++ b/src/net/tortuga/animations/LeavesAnimator.java @@ -0,0 +1,137 @@ +package net.tortuga.animations; + + +import static org.lwjgl.opengl.GL11.*; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Random; +import java.util.Set; + +import net.tortuga.App; +import net.tortuga.textures.TextureManager; +import net.tortuga.textures.Textures; +import net.tortuga.util.AnimDouble; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.HSV; +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.coord.Rect; +import com.porcupine.coord.Vec; + + +public class LeavesAnimator implements IRenderer { + + private static final double FALL_SPEED = 0.3; + private static Random rand = new Random(); + private Set leaves = new HashSet(); + + private static class Leaf { + + CoordI index = new CoordI(rand.nextInt(4), rand.nextInt(4)); + int width = 64; + AnimDouble angle = new AnimDouble(rand.nextInt(360)); + double rotation = (0.3 + rand.nextDouble() * 0.9) * (rand.nextBoolean() ? 1 : -1); + int appW = (int) App.inst.getSize().x; + Coord pos = new Coord(-appW / 2 + rand.nextInt(appW), -20 - rand.nextInt(100)); + Vec move = new Vec(-0.8 + rand.nextDouble() * 1.6, FALL_SPEED - 0.07 + rand.nextDouble() * 0.15); + double size = (1.7 + rand.nextDouble() * 1) * 22; + Rect rect = new Rect().grow_ip(0.5, 0.5); + + Rect txCoord = new Rect(index.x * width, index.y * width, (index.x + 1) * width, (index.y + 1) * width); + + RGB color = new HSV(0.05 + rand.nextDouble() * 0.21, 0.95, 0.5 + 0.20 * rand.nextDouble()).toRGB(); + + + public void render(double delta) + { + pos.update(delta); + angle.update(delta); + + if (pos.isFinished()) { + pos.remember(); + pos.add_ip(move); + pos.animate(0.02); + } + + if (angle.isFinished()) { + angle.addValue(rotation, 0.03); + } + + glPushMatrix(); + Coord coord = pos.getDelta().mul(1, -1, 1).add(0, App.inst.getSize().y); + glTranslated(App.inst.getSize().x / 2 + coord.x, coord.y, coord.z); + glRotated(angle.delta(), 0, 0, 1); + glScaled(size, size, size); + RenderUtils.setColor(color); + RenderUtils.quadTexturedAbs(rect, txCoord.div(256)); + glPopMatrix(); + + } + } + + + public LeavesAnimator() { + initFill(); + } + + + private void initFill() + { + for (int i = 0; i < 30 + (App.isFullscreen() ? 30 : 0); i++) { + Leaf sf; + leaves.add(sf = new Leaf()); + int appW = (int) App.inst.getSize().x; + int appH = (int) App.inst.getSize().y; + sf.pos.setTo(-appW / 2 + rand.nextInt(appW), rand.nextInt(appH)); + } + } + + + @Override + public void onFullscreenChange() + { + leaves.clear(); + initFill(); + } + + + @Override + public void updateGui() + { + Rect screen = new Rect(App.inst.getSize()).grow_ip(30, 30); + + Iterator i = leaves.iterator(); + while (i.hasNext()) { + Leaf sf = i.next(); + if (sf.pos.y > screen.getSize().y) { + i.remove(); + } + } + + } + + + @Override + public void render(double delta) + { + if (leaves.size() < (App.isFullscreen() ? 66 : 36)) { + leaves.add(new Leaf()); + } + + glPushAttrib(GL_ENABLE_BIT); + + glEnable(GL_TEXTURE_2D); + RenderUtils.setColor(RGB.WHITE); + + TextureManager.bind(Textures.leaves); + for (Leaf sf : leaves) { + sf.render(delta); + } + + glPopAttrib(); + + } +} diff --git a/src/net/tortuga/animations/RainAnimator.java b/src/net/tortuga/animations/RainAnimator.java new file mode 100644 index 0000000..2398999 --- /dev/null +++ b/src/net/tortuga/animations/RainAnimator.java @@ -0,0 +1,144 @@ +package net.tortuga.animations; + + +import static org.lwjgl.opengl.GL11.*; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Random; +import java.util.Set; + +import net.tortuga.App; +import net.tortuga.textures.TextureManager; +import net.tortuga.textures.Textures; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.HSV; +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.coord.Rect; +import com.porcupine.coord.Vec; +import com.porcupine.math.Calc.Rad; + + +public class RainAnimator implements IRenderer { + + private static final Vec FALL_VEC = new Vec(-2, 10); + private static Random rand = new Random(); + private Set raindrops = new HashSet(); + private long nsStart; + + private class Raindrop { + + CoordI index = new CoordI(rand.nextInt(4), rand.nextInt(4)); + int width = 64; + + int appW = (int) (App.inst.getSize().x * 1.3); + Coord pos = new Coord(-appW / 2 + rand.nextInt(appW), -20 - rand.nextInt(100)); + + double size = (2 + rand.nextDouble() * 0.3) * 20; + Rect rect = new Rect().grow_ip(0.5, 0.5); + + Rect txCoord = new Rect(index.x * width, index.y * width, (index.x + 1) * width, (index.y + 1) * width); + + RGB color = new HSV(0.53 + rand.nextDouble() * 0.06, 0.72, 0.7 + 0.1 * rand.nextDouble()).toRGB(); + + double time = (System.nanoTime() - nsStart) / 1000000000D; + + double x = Math.sin(time / (30)); + + Vec move = (Vec) FALL_VEC.scale(0.7 + rand.nextDouble() * 0.6).mul_ip(x, 1, 1); + + Vec[] translates = { Vec.random(0, 0), Vec.random(200, 400), Vec.random(200, 400), Vec.random(200, 400) }; + + + public void render(double delta) + { + pos.update(delta); + + if (pos.isFinished()) { + pos.remember(); + pos.add_ip(move); + pos.animate(0.02); + } + + glPushMatrix(); + Coord coord = pos.getDelta().mul(1, -1, 1).add(0, App.inst.getSize().y); + glTranslated(App.inst.getSize().x / 2 + coord.x, coord.y, coord.z); + glRotated(Rad.toDeg(Math.atan(move.x / move.y)), 0, 0, 1); + glScaled(size, size, size); + RenderUtils.setColor(color); + for (Vec v : translates) { + glTranslated(v.x, v.y, 0); + RenderUtils.quadTexturedAbs(rect, txCoord.div(256)); + } + glPopMatrix(); + + } + } + + + public RainAnimator() { + nsStart = System.nanoTime(); + initFill(); + } + + + private void initFill() + { + for (int i = 0; i < 250 + (App.isFullscreen() ? 100 : 0); i++) { + Raindrop sf; + raindrops.add(sf = new Raindrop()); + int appW = (int) App.inst.getSize().x; + int appH = (int) App.inst.getSize().y; + sf.pos.setTo(-appW / 2 + rand.nextInt(appW), rand.nextInt(appH)); + } + } + + + @Override + public void onFullscreenChange() + { + raindrops.clear(); + initFill(); + } + + + @Override + public void updateGui() + { + Rect screen = new Rect(App.inst.getSize()).grow_ip(30, 30); + + Iterator i = raindrops.iterator(); + while (i.hasNext()) { + Raindrop sf = i.next(); + if (sf.pos.y > screen.getSize().y) { + i.remove(); + } + } + + } + + + @Override + public void render(double delta) + { + if (raindrops.size() < (App.isFullscreen() ? 350 : 250)) { + raindrops.add(new Raindrop()); + } + + glPushAttrib(GL_ENABLE_BIT); + + glEnable(GL_TEXTURE_2D); + RenderUtils.setColor(RGB.WHITE); + + TextureManager.bind(Textures.rain); + for (Raindrop sf : raindrops) { + sf.render(delta); + } + + glPopAttrib(); + + } +} diff --git a/src/net/tortuga/animations/SnowAnimator.java b/src/net/tortuga/animations/SnowAnimator.java new file mode 100644 index 0000000..3e9ea2f --- /dev/null +++ b/src/net/tortuga/animations/SnowAnimator.java @@ -0,0 +1,137 @@ +package net.tortuga.animations; + + +import static org.lwjgl.opengl.GL11.*; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Random; +import java.util.Set; + +import net.tortuga.App; +import net.tortuga.textures.TextureManager; +import net.tortuga.textures.Textures; +import net.tortuga.util.AnimDouble; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.HSV; +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.coord.Rect; +import com.porcupine.coord.Vec; + + +public class SnowAnimator implements IRenderer { + + private static final double FALL_SPEED = 0.3; + private static Random rand = new Random(); + private Set snowflakes = new HashSet(); + + private static class Snowflake { + + CoordI index = new CoordI(rand.nextInt(4), rand.nextInt(4)); + int width = 64; + AnimDouble angle = new AnimDouble(rand.nextInt(360)); + double rotation = (0.2 + rand.nextDouble() * 0.7) * (rand.nextBoolean() ? 1 : -1); + int appW = (int) App.inst.getSize().x; + Coord pos = new Coord(-appW / 2 + rand.nextInt(appW), -20); + Vec move = new Vec(-0.2 + rand.nextDouble() * 0.4, FALL_SPEED); + double size = (1 + rand.nextDouble() * 0.4) * 22; + Rect rect = new Rect().grow_ip(0.5, 0.5); + + Rect txCoord = new Rect(index.x * width, index.y * width, (index.x + 1) * width, (index.y + 1) * width); + + RGB color = new HSV(0.597 - 0.05 + rand.nextDouble() * 0.1, 0.14 * rand.nextDouble(), 1).toRGB(); + + + public void render(double delta) + { + pos.update(delta); + angle.update(delta); + + if (pos.isFinished()) { + pos.remember(); + pos.add_ip(move); + pos.animate(0.02); + } + + if (angle.isFinished()) { + angle.addValue(rotation, 0.03); + } + + glPushMatrix(); + Coord coord = pos.getDelta().mul(1, -1, 1).add(0, App.inst.getSize().y); + glTranslated(App.inst.getSize().x / 2 + coord.x, coord.y, coord.z); + glRotated(angle.delta(), 0, 0, 1); + glScaled(size, size, size); + RenderUtils.setColor(color); + RenderUtils.quadTexturedAbs(rect, txCoord.div(256)); + glPopMatrix(); + + } + } + + + public SnowAnimator() { + initFill(); + } + + + private void initFill() + { + for (int i = 0; i < 50 + (App.isFullscreen() ? 50 : 0); i++) { + Snowflake sf; + snowflakes.add(sf = new Snowflake()); + int appW = (int) App.inst.getSize().x; + int appH = (int) App.inst.getSize().y; + sf.pos.setTo(-appW / 2 + rand.nextInt(appW), rand.nextInt(appH)); + } + } + + + @Override + public void onFullscreenChange() + { + snowflakes.clear(); + initFill(); + } + + + @Override + public void updateGui() + { + Rect screen = new Rect(App.inst.getSize()).grow_ip(30, 30); + + Iterator i = snowflakes.iterator(); + while (i.hasNext()) { + Snowflake sf = i.next(); + if (sf.pos.y > screen.getSize().y) { + i.remove(); + } + } + + } + + + @Override + public void render(double delta) + { + if (rand.nextInt(5) == 0 && snowflakes.size() < (App.isFullscreen() ? 100 : 50)) { + snowflakes.add(new Snowflake()); + } + + glPushAttrib(GL_ENABLE_BIT); + + glEnable(GL_TEXTURE_2D); + RenderUtils.setColor(RGB.WHITE); + + TextureManager.bind(Textures.snow); + for (Snowflake sf : snowflakes) { + sf.render(delta); + } + + glPopAttrib(); + + } +} diff --git a/src/net/tortuga/animations/WaterAnimator.java b/src/net/tortuga/animations/WaterAnimator.java new file mode 100644 index 0000000..f476400 --- /dev/null +++ b/src/net/tortuga/animations/WaterAnimator.java @@ -0,0 +1,161 @@ +package net.tortuga.animations; + + +import static org.lwjgl.opengl.GL11.*; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Random; +import java.util.Set; + +import net.tortuga.App; +import net.tortuga.textures.TextureManager; +import net.tortuga.textures.Textures; +import net.tortuga.util.AnimDouble; +import net.tortuga.util.RenderUtils; + +import org.lwjgl.opengl.GL14; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; +import com.porcupine.coord.Vec; +import com.porcupine.math.Calc; + + +/** + * Map water animator + * + * @author MightyPork + */ +public class WaterAnimator implements IRenderer { + + private static Random rand = new Random(); + private Set circles = new HashSet(); + private long nsStart; + + private class Circle { + + int appW = (int) App.inst.getSize().x; + int appH = (int) App.inst.getSize().y; + Coord pos; + + AnimDouble size = new AnimDouble(1); + + boolean dead; + + Vec[] translates = { Vec.random(0, 0), Vec.random(200, 400), Vec.random(200, 400), Vec.random(200, 400) }; + + Rect rect = new Rect().grow_ip(0.5, 0.5); + + + public Circle() { + pos = new Coord(-appW / 2 + rand.nextInt(appW), -appH / 2 + rand.nextInt(appH)); + pos.sub_ip(0, getCurrentWaterOffset().y); + + double maxS = (0.4 + rand.nextDouble() * 1.2) * 140; + size.addValue(maxS, maxS / 30); + } + + + //RGB color = new HSV(0.597-0.05+rand.nextDouble()*0.1, 0.14*rand.nextDouble(), 1).toRGB(); + + public void render(double delta) + { + size.update(delta); + + if (size.isFinished()) dead = true; + + glPushMatrix(); + Coord coord = pos; + glTranslated(App.inst.getSize().x / 2 + coord.x, App.inst.getSize().y / 2 + coord.y, coord.z); + + RGB color = new RGB(0x123B82, Calc.clampd((1D - size.getRatio()) * 0.8, 0, 0.4)); + double sc = size.delta(); + RenderUtils.setColor(color); + + for (Vec v : translates) { + glTranslated(v.x, v.y, 0); + RenderUtils.quadTexturedAbs(rect.mul(sc, sc * 0.8), Rect.ONE); + } + + glPopMatrix(); + + } + } + + + /** + * Make new animator + */ + public WaterAnimator() { + nsStart = System.nanoTime(); + } + + + @Override + public void onFullscreenChange() + {} + + + @Override + public void updateGui() + { + Iterator i = circles.iterator(); + while (i.hasNext()) { + Circle sf = i.next(); + if (sf.dead) { + i.remove(); + } + } + + } + + /** Map offset (window offset) */ + public Coord offsetPlus = new Coord(0, 0); + + + private Coord getCurrentWaterOffset() + { + double time = (System.nanoTime() - nsStart) / 1000000000D; + + double x = Math.sin(time / (30)) * 100D; + double y = (time / (50)) * 256D; + return offsetPlus.add(x, y); + } + + + @Override + public void render(double delta) + { + if (circles.size() < (App.isFullscreen() ? 35 : 20)) { + circles.add(new Circle()); + } + + glPushAttrib(GL_ENABLE_BIT); + glPushMatrix(); + + Rect quad = new Rect(App.inst.getSize()); + Rect quadT = new Rect(App.inst.getSize()); + quadT.add_ip(getCurrentWaterOffset().mul(-1, 1, 1)); + + RenderUtils.quadTextured(quad, quadT, Textures.water); + + GL14.glBlendFuncSeparate(GL_ONE, GL_SRC_ALPHA, GL_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnable(GL_TEXTURE_2D); + + RenderUtils.translate(getCurrentWaterOffset()); + + TextureManager.bind(Textures.circle); + for (Circle sf : circles) { + sf.render(delta); + } + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glPopMatrix(); + glPopAttrib(); + + } +} diff --git a/src/net/tortuga/annotations/Internal.java b/src/net/tortuga/annotations/Internal.java new file mode 100644 index 0000000..0785d55 --- /dev/null +++ b/src/net/tortuga/annotations/Internal.java @@ -0,0 +1,11 @@ +package net.tortuga.annotations; + + +/** + * Describes internal method or field which should not be accessed from outside.
+ * Used where private is not possible, but public does not mean available for + * anyone. + * + * @author MightyPork + */ +public @interface Internal {} diff --git a/src/net/tortuga/annotations/NeedsOverride.java b/src/net/tortuga/annotations/NeedsOverride.java new file mode 100644 index 0000000..62064cf --- /dev/null +++ b/src/net/tortuga/annotations/NeedsOverride.java @@ -0,0 +1,9 @@ +package net.tortuga.annotations; + + +/** + * Describes an incomplete method or class + * + * @author MightyPork + */ +public @interface NeedsOverride {} diff --git a/src/net/tortuga/annotations/Unimplemented.java b/src/net/tortuga/annotations/Unimplemented.java new file mode 100644 index 0000000..a0c7562 --- /dev/null +++ b/src/net/tortuga/annotations/Unimplemented.java @@ -0,0 +1,9 @@ +package net.tortuga.annotations; + + +/** + * Marks unimplemented method, which is not abstract. + * + * @author MightyPork + */ +public @interface Unimplemented {} diff --git a/src/net/tortuga/annotations/Unused.java b/src/net/tortuga/annotations/Unused.java new file mode 100644 index 0000000..bbfe131 --- /dev/null +++ b/src/net/tortuga/annotations/Unused.java @@ -0,0 +1,10 @@ +package net.tortuga.annotations; + + +/** + * Annotation for apparently unused methods, to indicate that they can be safely + * removed when preparing the game for final release + * + * @author MightyPork + */ +public @interface Unused {} diff --git a/src/net/tortuga/fonts/FontManager.java b/src/net/tortuga/fonts/FontManager.java new file mode 100644 index 0000000..999941c --- /dev/null +++ b/src/net/tortuga/fonts/FontManager.java @@ -0,0 +1,318 @@ +package net.tortuga.fonts; + + +import java.awt.Font; +import java.io.InputStream; +import java.util.HashMap; + +import net.tortuga.Constants; +import net.tortuga.util.Log; + +import org.newdawn.slick.util.ResourceLoader; + +import com.porcupine.coord.Coord; + + +/** + * Remade universal font manager for Sector. + * + * @author MightyPork + */ +public class FontManager { + + private static final boolean DEBUG = Constants.LOG_FONTS; + + /** + * Glyph tables. + * + * @author MightyPork + */ + public static class Glyphs { + + //@formatter:off + /** all glyphs */ + public static final String all = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]" + + "^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£¤" + + "¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäå" + + "æçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; + + /** letters and numbers, sufficient for basic messages etc. NO SPACE */ + public static final String alnum_nospace = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + /** letters and numbers, sufficient for basic messages etc. */ + public static final String alnum = + " "+alnum_nospace; + + /** letters and numbers with the most basic punctuation signs */ + public static final String basic_text = + " .-,.?!:;_"+alnum_nospace; + + /** letters */ + public static final String alpha = + " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + /** numbers */ + public static final String numbers = + " 0123456789.-,:"; + + /** non-standard variants of alnum */ + public static final String alnum_extra = + " ŒÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜŸÝßàáâãäåæçèéêëìíîïðñòóôõöøùúû" + + "üýþÿĚŠČŘŽŤŇĎŮěščřžťňďůŁłđ"; + + /** signs and punctuation */ + public static final String signs = + " !\"#$%&§'()*+,-./:;<=>?@[\\]^_{|}~"; + + /** extra signs and punctuation */ + public static final String signs_extra = + " ¥€£¢`ƒ„…†‡ˆ‰‹‘’“”•›¡¤¦¨ª«¬­¯°±²³´µ¶·¸¹º»¼½¾¿÷™©­®→↓←↑"; + + + /** basic character set. */ + public static final String basic = alnum + signs; + //@formatter:on + } + + /** + * Font style + * + * @author MightyPork + */ + public static enum Style + { + /** Normal */ + NORMAL, + /** Italic */ + ITALIC, + /** Stronger italic to left. */ + LEFT, + /** Stronger italic to right */ + RIGHT, + /** Monospace type */ + MONO, + /** Bold */ + BOLD, + /** Bold & Italic */ + BOLD_I, + /** Bold & Left */ + BOLD_L, + /** Bold & Right */ + BOLD_R, + /** Heavy style, stronger than bold. */ + HEAVY, + /** Light (lighter than normal) */ + LIGHT, + /** narrow style, similar to Light */ + NARROW, + /** Wide style, like Bold but with thinner lines */ + WIDE, + /** Outline variant of normal */ + OUTLINE; + } + + /** + * Preloaded font identifier [name, size, style] + * + * @author MightyPork + */ + public static class FontId { + + /** font size (pt) */ + public float size = 24; + /** font name, registered with registerFile */ + public String name = ""; + /** font style. The given style must be in a file. */ + public Style style; + + /** Set of glyphs in this ID */ + public String glyphs = ""; + + /** Index for faster comparision of glyph ids. */ + public int glyphset_id = 0; + + + /** + * Preloaded font identifier + * + * @param name font name (registerFile) + * @param size font size (pt) + * @param style font style + * @param glyphs glyphs to load + */ + public FontId(String name, double size, Style style, String glyphs) { + this.name = name; + this.size = (float) size; + this.style = style; + + if (glyphs.equals(Glyphs.basic)) { + glyphset_id = 1; + } else if (glyphs.equals(Glyphs.alnum)) { + glyphset_id = 2; + } else if (glyphs.equals(Glyphs.basic_text)) { + glyphset_id = 3; + } else if (glyphs.equals(Glyphs.numbers)) { + glyphset_id = 4; + } else if (glyphs.equals(Glyphs.alpha)) { + glyphset_id = 5; + } else if (glyphs.equals(Glyphs.all)) { + glyphset_id = 6; + } else if (glyphs.equals(Glyphs.alnum_extra)) { + glyphset_id = 7; + } else if (glyphs.equals(Glyphs.signs)) { + glyphset_id = 8; + } else if (glyphs.equals(Glyphs.signs_extra)) { + glyphset_id = 9; + } else { + this.glyphs = glyphs; + } + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj.getClass().isAssignableFrom(getClass()))) return false; + if (obj instanceof FontId) { + if (obj == this) return true; + FontId id2 = ((FontId) obj); + boolean flag = true; + flag &= id2.size == size; + flag &= id2.name.equals(name); + flag &= id2.style == style; + flag &= ((id2.glyphset_id != -1 && id2.glyphset_id == glyphset_id) || id2.glyphs.equals(glyphs)); + return flag; + } + return false; + } + + + @Override + public int hashCode() + { + return (new Float(size).hashCode()) ^ name.hashCode() ^ style.hashCode() ^ glyphset_id; + } + + + @Override + public String toString() + { + return "[" + name + ", " + size + ", " + style + (glyphset_id > 0 ? ", g=" + glyphset_id : ", g=custom") + "]"; + } + } + + /** + * Group of styles of one font. + * + * @author MightyPork + */ + public static class FontFamily extends HashMap { + } + + /** + * Table of font files. name → {style:file,style:file,style:file...} + */ + private static HashMap fontFiles = new HashMap(); + + + /** + * Register font file. + * + * @param path resource path (res/fonts/...) + * @param name font name (for binding) + * @param style font style in this file + */ + public static void registerFile(String path, String name, Style style) + { + if (fontFiles.containsKey(name)) { + if (fontFiles.get(name) != null) { + fontFiles.get(name).put(style, path); + return; + } + } + + // insert new table of styles to font name. + FontFamily family = new FontFamily(); + family.put(style, path); + fontFiles.put(name, family); + } + + /** Counter of loaded fonts */ + public static int loadedFontCounter = 0; + + + /** + * Preload font if needed, get preloaded font.
+ * If needed file is not available, throws runtime exception. + * + * @param name font name (registerFile) + * @param size font size (pt) + * @param style font style + * @param glyphs glyphs needed + * @return the loaded font. + */ + public static LoadedFont loadFont(String name, double size, Style style, String glyphs) + { + return loadFont(name, size, style, glyphs, 9, 8, Coord.ONE, 0, 0); + } + + + /** + * Preload font if needed, get preloaded font.
+ * If needed file is not available, throws runtime exception. + * + * @param name font name (registerFile) + * @param size font size (pt) + * @param style font style + * @param glyphs glyphs needed + * @param correctLeft left horizontal correction + * @param correctRight right horizontal correction + * @param scale font scale (changing aspect ratio) + * @param clipTop top clip (0-1) - top part of the font to be cut off + * @param clipBottom bottom clip (0-1) - bottom part of the font to be cut + * off + * @return the loaded font. + */ + public static LoadedFont loadFont(String name, double size, Style style, String glyphs, int correctLeft, int correctRight, Coord scale, double clipTop, double clipBottom) + { + String resourcePath; + try { + resourcePath = fontFiles.get(name).get(style); + if (resourcePath == null) { + Log.w("Font [" + name + "] does not have variant " + style + ".\nUsing NORMAL instead."); + resourcePath = fontFiles.get(name).get(Style.NORMAL); + if (resourcePath == null) { + throw new NullPointerException(); + } + } + } catch (NullPointerException npe) { + throw new RuntimeException("Font loading failed: no font file registered for name \"" + name + "\"."); + } + + InputStream in = ResourceLoader.getResourceAsStream(resourcePath); + + Font awtFont; + try { + awtFont = Font.createFont(Font.TRUETYPE_FONT, in); + } catch (Exception e) { + Log.e("Loading of font " + resourcePath + " failed.", e); + throw new RuntimeException(e); + } + + awtFont = awtFont.deriveFont((float) size); // set font size + LoadedFont font = new LoadedFont(awtFont, true, glyphs); + + font.setCorrection(correctLeft, correctRight); + font.setClip(clipTop, clipBottom); + font.setScale(scale.x, scale.y); + + loadedFontCounter++; + + if (DEBUG) Log.f3("Font from file \"" + resourcePath + "\" preloaded."); + + return font; + } +} diff --git a/src/net/tortuga/fonts/Fonts.java b/src/net/tortuga/fonts/Fonts.java new file mode 100644 index 0000000..29d64f2 --- /dev/null +++ b/src/net/tortuga/fonts/Fonts.java @@ -0,0 +1,61 @@ +package net.tortuga.fonts; + + +import static net.tortuga.fonts.FontManager.Style.*; +import net.tortuga.fonts.FontManager.Glyphs; +import net.tortuga.util.Log; + + +/** + * Global font preloader + * + * @author Rapus + */ +@SuppressWarnings("javadoc") +public class Fonts { + + public static LoadedFont splash_info; + + public static LoadedFont tooltip; + public static LoadedFont gui; + public static LoadedFont gui_title; + public static LoadedFont program_number; + public static LoadedFont menu_button; + public static LoadedFont menu_title; + public static LoadedFont tiny; + + + private static void registerFileNames() + { + FontManager.registerFile("res/fonts/4feb.ttf", "4feb", NORMAL); + } + + + /** + * Load fonts needed for splash. + */ + public static void loadForSplash() + { + registerFileNames(); + + gui = FontManager.loadFont("4feb", 24, NORMAL, Glyphs.basic).setCorrection(8, 7); + splash_info = FontManager.loadFont("4feb", 42, NORMAL, "Loading."); + } + + + /** + * Preload all fonts we will use in the game + */ + public static void load() + { + tooltip = FontManager.loadFont("4feb", 24, NORMAL, Glyphs.basic_text).setCorrection(8, 7); + gui_title = FontManager.loadFont("4feb", 30, NORMAL, Glyphs.basic_text); + menu_button = FontManager.loadFont("4feb", 36, NORMAL, Glyphs.basic_text); + menu_title = FontManager.loadFont("4feb", 34, NORMAL, Glyphs.basic_text); + program_number = FontManager.loadFont("4feb", 28, NORMAL, Glyphs.numbers); + tiny = FontManager.loadFont("4feb", 20, NORMAL, Glyphs.basic_text); + + Log.i("Fonts loaded = " + FontManager.loadedFontCounter); + + } +} diff --git a/src/net/tortuga/fonts/LoadedFont.java b/src/net/tortuga/fonts/LoadedFont.java new file mode 100644 index 0000000..5e7d122 --- /dev/null +++ b/src/net/tortuga/fonts/LoadedFont.java @@ -0,0 +1,667 @@ +package net.tortuga.fonts; + + +import static net.tortuga.util.Align.*; +import static org.lwjgl.opengl.GL11.*; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.tortuga.Constants; +import net.tortuga.util.Log; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.util.glu.GLU; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; + + +/** + * A TrueType font implementation originally for Slick, edited for Bobjob's + * Engine + * + * @original author James Chambers (Jimmy) + * @original author Jeremy Adams (elias4444) + * @original author Kevin Glass (kevglass) + * @original author Peter Korzuszek (genail) + * @new version edited by David Aaron Muhar (bobjob) + * @new version edited by MightyPork + */ +@SuppressWarnings("javadoc") +public class LoadedFont { + + private static final boolean DEBUG = Constants.LOG_FONTS; + + /** Map of user defined font characters (Character <-> IntObject) */ + private Map chars = new HashMap(100); + + /** Boolean flag on whether AntiAliasing is enabled or not */ + private boolean antiAlias; + + /** Font's size */ + private int fontSize = 0; + + /** Font's height */ + private int fontHeight = 0; + + /** Texture used to cache the font 0-255 characters */ + private int fontTextureID; + + /** Default font texture width */ + private int textureWidth = 2048; + + /** Default font texture height */ + private int textureHeight = 2048; + + /** A reference to Java's AWT Font that we create our font texture from */ + private Font font; + + /** The font metrics for our Java AWT font */ + private FontMetrics fontMetrics; + + private int correctL = 9, correctR = 8; + + private double defScaleX = 1, defScaleY = 1; + private double clipVerticalT = 0; + private double clipVerticalB = 0; + + private class CharStorageEntry { + + /** Character's width */ + public int width; + + /** Character's height */ + public int height; + + /** Character's stored x position */ + public int texPosX; + + /** Character's stored y position */ + public int texPosY; + } + + + public LoadedFont(Font font, boolean antiAlias, String charsNeeded) { + this.font = font; + this.fontSize = font.getSize() + 3; + this.antiAlias = antiAlias; + + createSet(charsNeeded.toCharArray()); + + fontHeight -= 1; + if (fontHeight <= 0) fontHeight = 1; + } + + + public void setCorrection(boolean on) + { + if (on) { + correctL = 9;//2 + correctR = 8;//1 + } else { + correctL = 0; + correctR = 0; + } + } + + + private BufferedImage getFontImage(char ch) + { + // Create a temporary image to extract the character's size + BufferedImage tempfontImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = (Graphics2D) tempfontImage.getGraphics(); + if (antiAlias == true) { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + g.setFont(font); + fontMetrics = g.getFontMetrics(); + int charwidth = fontMetrics.charWidth(ch) + 8; + + if (charwidth <= 0) { + charwidth = 7; + } + int charheight = fontMetrics.getHeight() + 3; + if (charheight <= 0) { + charheight = fontSize; + } + + // Create another image holding the character we are creating + BufferedImage fontImage; + fontImage = new BufferedImage(charwidth, charheight, BufferedImage.TYPE_INT_ARGB); + Graphics2D gt = (Graphics2D) fontImage.getGraphics(); + if (antiAlias == true) { + gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + gt.setFont(font); + + gt.setColor(Color.WHITE); + int charx = 3; + int chary = 1; + gt.drawString(String.valueOf(ch), (charx), (chary) + fontMetrics.getAscent()); + + return fontImage; + + } + + + private void createSet(char[] charsToLoad) + { + try { + class LoadedGlyph { + + public char c; + public BufferedImage image; + public int width; + public int height; + + + public LoadedGlyph(char c, BufferedImage image) { + this.image = image; + this.c = c; + this.width = image.getWidth(); + this.height = image.getHeight(); + } + } + + List glyphs = new ArrayList(); + List loaded = new ArrayList(); + for (char ch : charsToLoad) { + if (!loaded.contains(ch)) { + glyphs.add(new LoadedGlyph(ch, getFontImage(ch))); + loaded.add(ch); + } + } + + Coord canvas = new Coord(128, 128); + double lineHeight = 0; + Coord begin = new Coord(0, 0); + boolean needsLarger = false; + + while (true) { + needsLarger = false; + + for (LoadedGlyph glyph : glyphs) { + if (begin.x + glyph.width > canvas.x) { + begin.y += lineHeight; + lineHeight = 0; + begin.x = 0; + } + + if (lineHeight < glyph.height) { + lineHeight = glyph.height; + } + + if (begin.y + lineHeight > canvas.y) { + needsLarger = true; + break; + } + + // draw. + begin.x += glyph.width; + } + + if (needsLarger) { + canvas.x *= 2; + canvas.y *= 2; + begin.setTo(0, 0); + lineHeight = 0; + } else { + if (DEBUG) Log.f3("Preparing texture " + canvas.x + "x" + canvas.y); + break; + } + } + + textureWidth = (int) canvas.x; + textureHeight = (int) canvas.y; + + BufferedImage imgTemp = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = (Graphics2D) imgTemp.getGraphics(); + + g.setColor(new Color(0, 0, 0, 1)); + g.fillRect(0, 0, textureWidth, textureHeight); + + int rowHeight = 0; + int positionX = 0; + int positionY = 0; + + for (LoadedGlyph glyph : glyphs) { + CharStorageEntry storedChar = new CharStorageEntry(); + + storedChar.width = glyph.width; + storedChar.height = glyph.height; + + if (positionX + storedChar.width >= textureWidth) { + positionX = 0; + positionY += rowHeight; + rowHeight = 0; + } + + storedChar.texPosX = positionX; + storedChar.texPosY = positionY; + + if (storedChar.height > fontHeight) { + fontHeight = storedChar.height; + } + + if (storedChar.height > rowHeight) { + rowHeight = storedChar.height; + } + + // Draw it here + g.drawImage(glyph.image, positionX, positionY, null); + + positionX += storedChar.width; + + chars.put(glyph.c, storedChar); + } + + fontTextureID = loadImage(imgTemp); + + imgTemp = null; + + } catch (Exception e) { + System.err.println("Failed to create font."); + e.printStackTrace(); + } + } + + + private void drawQuad(double drawX, double drawY, double drawX2, double drawY2, CharStorageEntry charObj) + { + double srcX = charObj.texPosX + charObj.width; + double srcY = charObj.texPosY + charObj.height; + double srcX2 = charObj.texPosX; + double srcY2 = charObj.texPosY; + double DrawWidth = drawX2 - drawX; + double DrawHeight = drawY2 - drawY; + double TextureSrcX = srcX / textureWidth; + double TextureSrcY = srcY / textureHeight; + double SrcWidth = srcX2 - srcX; + double SrcHeight = srcY2 - srcY; + double RenderWidth = (SrcWidth / textureWidth); + double RenderHeight = (SrcHeight / textureHeight); + + drawY -= DrawHeight * clipVerticalB; + + GL11.glTexCoord2d(TextureSrcX, TextureSrcY); + GL11.glVertex2d(drawX, drawY); + GL11.glTexCoord2d(TextureSrcX, TextureSrcY + RenderHeight); + GL11.glVertex2d(drawX, drawY + DrawHeight); + GL11.glTexCoord2d(TextureSrcX + RenderWidth, TextureSrcY + RenderHeight); + GL11.glVertex2d(drawX + DrawWidth, drawY + DrawHeight); + GL11.glTexCoord2d(TextureSrcX + RenderWidth, TextureSrcY); + GL11.glVertex2d(drawX + DrawWidth, drawY); + } + + + public int getWidth(String whatchars) + { + if (whatchars == null) whatchars = ""; + int totalwidth = 0; + CharStorageEntry charStorage = null; + char currentChar = 0; + for (int i = 0; i < whatchars.length(); i++) { + currentChar = whatchars.charAt(i); + + charStorage = chars.get(currentChar); + + if (charStorage != null) { + totalwidth += charStorage.width - correctL; + } + } + return (int) (totalwidth * defScaleX); + } + + + public int getHeight() + { + return (int) (fontHeight * defScaleY * (1 - clipVerticalT - clipVerticalB)); + } + + + public int getLineHeight() + { + return getHeight(); + } + + + public void drawString(double x, double y, String text, double scaleX, double scaleY, RGB color) + { + drawString(x, y, text, 0, text.length() - 1, scaleX, scaleY, color, LEFT); + } + + + public void drawString(double x, double y, String text, double scaleX, double scaleY, RGB color, int align) + { + drawString(x, y, text, 0, text.length() - 1, scaleX, scaleY, color, align); + } + + + private void drawString(double x, double y, String text, int startIndex, int endIndex, double scaleX, double scaleY, RGB color, int align) + { + x = Math.round(x); + y = Math.round(y); + + scaleX *= defScaleX; + scaleY *= defScaleY; + + CharStorageEntry charStorage = null; + int charCurrent; + + int totalwidth = 0; + int i = startIndex, d = 1, c = correctL; + float startY = 0; + + switch (align) { + case RIGHT: { + d = -1; + c = correctR; + + while (i < endIndex) { + if (text.charAt(i) == '\n') startY -= getHeight(); + i++; + } + break; + } + case CENTER: { + for (int l = startIndex; l <= endIndex; l++) { + charCurrent = text.charAt(l); + if (charCurrent == '\n') break; + + charStorage = chars.get((char) charCurrent); + if (charStorage != null) { + totalwidth += charStorage.width - correctL; + } + } + totalwidth /= -2; + break; + } + case LEFT: + default: { + d = 1; + c = correctL; + break; + } + + } + + GL11.glPushAttrib(GL_ENABLE_BIT); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTextureID); + GL11.glColor4d(color.r, color.g, color.b, color.a); + GL11.glBegin(GL11.GL_QUADS); + + while (i >= startIndex && i <= endIndex) { + charCurrent = text.charAt(i); + + charStorage = chars.get(new Character((char) charCurrent)); + + if (charStorage != null) { + if (d < 0) totalwidth += (charStorage.width - c) * d; + if (charCurrent == '\n') { + startY -= getHeight() * d; + totalwidth = 0; + if (align == CENTER) { + for (int l = i + 1; l <= endIndex; l++) { + charCurrent = text.charAt(l); + if (charCurrent == '\n') break; + + charStorage = chars.get((char) charCurrent); + + totalwidth += charStorage.width - correctL; + } + totalwidth /= -2; + } + //if center get next lines total width/2; + } else { + //@formatter:off + drawQuad( + (totalwidth + charStorage.width) * scaleX + x, + startY * scaleY + y, totalwidth * scaleX + x, + (startY + charStorage.height) * scaleY + y, + charStorage + ); + //@formatter:on + + if (d > 0) totalwidth += (charStorage.width - c) * d; + } + + } + + i += d; + } + GL11.glEnd(); + GL11.glPopAttrib(); + } + + + public static int loadImage(BufferedImage bufferedImage) + { + try { + short width = (short) bufferedImage.getWidth(); + short height = (short) bufferedImage.getHeight(); + //textureLoader.bpp = bufferedImage.getColorModel().hasAlpha() ? (byte)32 : (byte)24; + int bpp = (byte) bufferedImage.getColorModel().getPixelSize(); + ByteBuffer byteBuffer; + DataBuffer db = bufferedImage.getData().getDataBuffer(); + if (db instanceof DataBufferInt) { + int intI[] = ((DataBufferInt) (bufferedImage.getData().getDataBuffer())).getData(); + byte newI[] = new byte[intI.length * 4]; + for (int i = 0; i < intI.length; i++) { + byte b[] = intToByteArray(intI[i]); + int newIndex = i * 4; + + newI[newIndex] = b[1]; + newI[newIndex + 1] = b[2]; + newI[newIndex + 2] = b[3]; + newI[newIndex + 3] = b[0]; + } + + byteBuffer = ByteBuffer.allocateDirect(width * height * (bpp / 8)).order(ByteOrder.nativeOrder()).put(newI); + } else { + byteBuffer = ByteBuffer.allocateDirect(width * height * (bpp / 8)).order(ByteOrder.nativeOrder()).put(((DataBufferByte) (bufferedImage.getData().getDataBuffer())).getData()); + } + byteBuffer.flip(); + + int internalFormat = GL11.GL_RGBA8, format = GL11.GL_RGBA; + IntBuffer textureId = BufferUtils.createIntBuffer(1); + + GL11.glGenTextures(textureId); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId.get(0)); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + + GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); + + GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, internalFormat, width, height, format, GL11.GL_UNSIGNED_BYTE, byteBuffer); + return textureId.get(0); + + } catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + + return -1; + } + + + public static boolean isSupported(String fontname) + { + Font font[] = getFonts(); + for (int i = font.length - 1; i >= 0; i--) { + if (font[i].getName().equalsIgnoreCase(fontname)) return true; + } + return false; + } + + + public static Font[] getFonts() + { + return GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts(); + } + + + public static Font getFont(String fontname, int style, float size) + { + Font result = null; + GraphicsEnvironment graphicsenvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + for (Font font : graphicsenvironment.getAllFonts()) { + if (font.getName().equalsIgnoreCase(fontname)) { + result = font.deriveFont(style, size); + break; + } + } + return result; + } + + + public static byte[] intToByteArray(int value) + { + return new byte[] { (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value }; + } + + + public void destroy() + { + IntBuffer scratch = BufferUtils.createIntBuffer(1); + scratch.put(0, fontTextureID); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + GL11.glDeleteTextures(scratch); + } + + + public LoadedFont setScale(double x, double y) + { + defScaleX = x; + defScaleY = y; + return this; + } + + + public LoadedFont setClip(double clipRatioTop, double clipRatioBottom) + { + clipVerticalT = clipRatioTop; + clipVerticalB = clipRatioBottom; + return this; + } + + + public LoadedFont setCorrection(int correctionLeft, int correctionRight) + { + correctL = correctionLeft; + correctR = correctionRight; + return this; + } + + + /** + * Draw string with font. + * + * @param x x coord + * @param y y coord + * @param text text to draw + * @param color render color + * @param align (-1,0,1) + */ + public void draw(double x, double y, String text, RGB color, int align) + { + drawString(x, y, text, 1, 1, color, align); + } + + + /** + * Draw string with font. + * + * @param pos coord + * @param text text to draw + * @param color render color + * @param align (-1,0,1) + */ + public void draw(Coord pos, String text, RGB color, int align) + { + drawString(pos.x, pos.y, text, 1, 1, color, align); + } + + + /** + * Draw string with font. + * + * @param pos coord + * @param text text to draw + * @param color render color + * @param align (-1,0,1) + */ + public void draw(CoordI pos, String text, RGB color, int align) + { + drawString(pos.x, pos.y, text, 1, 1, color, align); + } + + + public void drawFuzzy(Coord pos, String text, int align, RGB textColor, RGB blurColor, int blurSize) + { + drawFuzzy(pos, text, align, textColor, blurColor, blurSize, true); + } + + + public void drawFuzzy(CoordI pos, String text, int align, RGB textColor, RGB blurColor, int blurSize) + { + drawFuzzy(pos.toCoord(), text, align, textColor, blurColor, blurSize, true); + } + + + public void drawFuzzy(CoordI pos, String text, int align, RGB textColor, RGB blurColor, int blurSize, boolean smooth) + { + drawFuzzy(pos.toCoord(), text, align, textColor, blurColor, blurSize, smooth); + } + + + public void drawFuzzy(Coord pos, String text, int align, RGB textColor, RGB blurColor, int blurSize, boolean smooth) + { + glPushMatrix(); + + glTranslated(pos.x, pos.y, pos.z); + + //shadow + int sh = blurSize; + + int l = glGenLists(1); + + glNewList(l, GL_COMPILE); + draw(0, 0, text, blurColor, align); + glEndList(); + + for (int xx = -sh; xx <= sh; xx += (smooth ? 1 : sh)) { + for (int yy = -sh; yy <= sh; yy += (smooth ? 1 : sh)) { + if (xx == 0 && yy == 0) continue; + glPushMatrix(); + glTranslated(xx, yy, 0); + glCallList(l); + glPopMatrix(); + } + } + + glDeleteLists(l, 1); + + draw(0, 0, text, textColor, align); + + glPopMatrix(); + } + +} diff --git a/src/net/tortuga/gui/panels/Panel.java b/src/net/tortuga/gui/panels/Panel.java new file mode 100644 index 0000000..7d42795 --- /dev/null +++ b/src/net/tortuga/gui/panels/Panel.java @@ -0,0 +1,347 @@ +package net.tortuga.gui.panels; + + +import static org.lwjgl.opengl.GL11.*; + +import java.util.Random; + +import net.tortuga.App; +import net.tortuga.animations.IRenderer; +import net.tortuga.annotations.Unimplemented; +import net.tortuga.gui.screens.Screen; +import net.tortuga.input.IInputHandler; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Vec; + + +/** + * Panel class, control module for screen. + * + * @author MightyPork + */ +public abstract class Panel implements IInputHandler, IRenderer { + + /** The screen this panel belongs to */ + public Screen screen; + + /** The application instance */ + public App app = App.inst; + + private Panel below = null; + + private Panel above = null; + + /** RNG */ + public Random rand = new Random(); + + + /** + * New panel + * + * @param screen parent screen + */ + public Panel(Screen screen) { + this.screen = screen; + } + + + /** + * @return true if this panel has no parent + */ + public final boolean isRoot() + { + return below == null; + } + + + /** + * @return true if this panel is the topmost, focused + */ + public final boolean isTop() + { + return above == null; + } + + + /** + * @return true if this panel is focused, topmost + */ + public final boolean isFocused() + { + return isTop(); + } + + + /** + * @return topmost panel in the panel stack + */ + public final Panel getTop() + { + if (isTop()) return this; + return above.getTop(); + } + + + /** + * @return panel below this one, if any + */ + public final Panel getBelow() + { + if (isRoot()) return null; + return below; + } + + + /** + * Destroy all panels above this panel. + */ + public final void destroyAbove() + { + if (!isTop()) { + above.destroyAbove(); + above.onBlur(); + above.onClose(); + above = null; + } + } + + + /** + * Close this panel (if not root panel), and give focus to the panel below. + */ + public final void closePanel() + { + if (!isRoot()) { + below.destroyAbove(); + onBlur(); + below.onFocus(); + } + } + + + /** + * Add a panel to the stack and focus it. + * + * @param added added child + */ + public final void openPanel(Panel added) + { + above = added; + above.below = this; + this.onBlur(); + above.onCreate(); + above.onFocus(); + } + + + /** + * Hook called when panel was openned + */ + public abstract void onCreate(); + + + /** + * @return true if this panel has dark translucent background. + */ + public abstract boolean hasBackgroundLayer(); + + + /** + * Get color of background layer + * + * @return color + */ + public RGB getBackgroundColor() + { + return new RGB(0, 0.7); + } + + + /** + * Called before the panel is closed. + */ + public abstract void onClose(); + + + /** + * Called when this panel is focused. + */ + @Unimplemented + public void onFocus() + {} + + + /** + * Called when this panel loses focus. + */ + @Unimplemented + public void onBlur() + {} + + + /** + * Update this panel.
+ * In case of root panel, you should update scene here. + */ + @Override + public abstract void updateGui(); + + + /** + * Render directly rendered 3D stuff in all panels + */ + public final void render3D() + { + renderDirect3D(); + if (!isTop()) { + getTop().render3D(); + } + } + + + /** + * Render the inside elements of this panel + */ + protected abstract void renderPanel(); + + + /** + * Render 3D stuff from this panel + */ + @Unimplemented + public void renderDirect3D() + {} + + + /** + * Called each update tick, if the mouse position was changed. + * + * @param pos + * @param move + */ + @Override + @Unimplemented + public void onMouseMove(Coord pos, Vec move, int wheelDelta) + {} + + + /** + * Mouse event handler. + * + * @param button button which caused this event + * @param down true = down, false = up + * @param wheelDelta number of steps the wheel turned since last event + * @param pos mouse position + * @param deltaPos delta maouse position + */ + @Override + @Unimplemented + public void onMouseButton(int button, boolean down, int wheelDelta, Coord pos, Coord deltaPos) + {} + + + /** + * Key event handler. + * + * @param key key index, constant Keyboard.KEY_??? + * @param c character typed, if any + * @param down true = down, false = up + */ + @Override + @Unimplemented + public void onKey(int key, char c, boolean down) + {} + + + /** + * In this method screen can handle static inputs, that is: + * Keyboard.isKeyDown, Mouse.isButtonDown etc. + */ + @Override + @Unimplemented + public void handleStaticInputs() + {} + + + /** + * Render all the panels in the stack, if this is the root panel. + */ + @Override + public final void render(double delta) + { + if (!isRoot()) return; + + glPushAttrib(GL_ENABLE_BIT); + glPushMatrix(); + + screen.render2D(delta); + if (screen.isWeatherEnabled()) { + App.weatherAnimation.render(delta); + } + + renderThisAndChildren(); + + glPopAttrib(); + glPopMatrix(); + + } + + + private final void renderThisAndChildren() + { + if (hasBackgroundLayer()) { + Coord size = app.getSize(); + + RenderUtils.setColor(getBackgroundColor()); + //glColor4d(0, 0, 0, 0.6); + glBegin(GL_QUADS); + glVertex2d(0, size.y); + glVertex2d(size.x, size.y); + glVertex2d(size.x, 0); + glVertex2d(0, 0); + glEnd(); + } + + renderPanel(); + + // render any higher guis + if (!isTop()) { + glTranslated(0, 0, 100); + getAbove().renderThisAndChildren(); + } + } + + + private Panel getAbove() + { + return above; + } + + + /** + * On window resized + */ + public abstract void onWindowChanged(); + + + @Override + @Deprecated + @Unimplemented + public final void onFullscreenChange() + {} + + + /** + * On viewport changed (fullscreen etc) + */ + public final void onViewportChanged() + { + onWindowChanged(); + if (!isTop()) { + getTop().onViewportChanged(); + } + } +} diff --git a/src/net/tortuga/gui/panels/PanelConfig.java b/src/net/tortuga/gui/panels/PanelConfig.java new file mode 100644 index 0000000..23452d9 --- /dev/null +++ b/src/net/tortuga/gui/panels/PanelConfig.java @@ -0,0 +1,184 @@ +package net.tortuga.gui.panels; + + +import net.tortuga.GameConfig; +import net.tortuga.gui.screens.Screen; +import net.tortuga.gui.widgets.Theme; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.gui.widgets.display.Text; +import net.tortuga.gui.widgets.input.Button; +import net.tortuga.gui.widgets.input.Checkbox; +import net.tortuga.gui.widgets.input.Slider; +import net.tortuga.gui.widgets.layout.Gap; +import net.tortuga.gui.widgets.layout.LayoutH; +import net.tortuga.gui.widgets.layout.LayoutV; +import net.tortuga.gui.widgets.layout.frame.FrameWindow; +import net.tortuga.sounds.SoundManager; +import net.tortuga.util.Align; + +import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.Display; + +import com.porcupine.color.RGB; + + +/** + * Overlay panel for paused game. + * + * @author MightyPork + */ +public class PanelConfig extends PanelGui { + + // FIXME add sliders for volume, add "toggle fullscreen" button + + private static final int SAVE = 0; + private static final int CANCEL = 1; + private Checkbox ckInitFullscreen; + private Checkbox ckVsync; + + private int soundVol = GameConfig.audioVolumeSound; + private int musicVol = GameConfig.audioVolumeMusic; + private Slider sliderSound; + private Slider sliderMusic; + private Button bnCancel; + private Button bnSave; + private Checkbox ckResizable; + private int origin; + + + /** + * @param screen + * @param origin 0 = menu, 1 = pause screen + */ + public PanelConfig(Screen screen, int origin) { + super(screen); + this.origin = origin; + } + + + @Override + public void initGui() + { + FrameWindow frame = Theme.mkWindow(); + + addGui(frame); + + LayoutV v = new LayoutV(Align.CENTER, Align.CENTER); + v.add(Theme.mkTitle("Settings")); + + LayoutV v2 = new LayoutV(Align.LEFT, Align.TOP); + + v2.add(ckInitFullscreen = new Checkbox(-1, "Start in fullscreen")); + v2.add(ckVsync = new Checkbox(-1, "Enable v-sync")); + v2.add(ckResizable = new Checkbox(-1, "Resizable window")); + + ckInitFullscreen.setChecked(GameConfig.startInFullscreen); + ckVsync.setChecked(GameConfig.enableVsync); + ckResizable.setChecked(GameConfig.enableResize); + + v2.add(new Gap(0, 15)); + + LayoutH h2; + + h2 = new LayoutH(Align.LEFT, Align.CENTER); + h2.add(new Text("Sound").setTextAlign(Align.RIGHT).setMinWidth(100)); + h2.add(new Gap(5, 0)); + h2.add(sliderSound = new Slider(250, soundVol / 100D)); + v2.add(h2); + + h2 = new LayoutH(Align.LEFT, Align.CENTER); + h2.add(new Text("Music").setTextAlign(Align.RIGHT).setMinWidth(100)); + h2.add(new Gap(5, 0)); + h2.add(sliderMusic = new Slider(250, musicVol / 100D)); + v2.add(h2); + v2.add(new Gap(0, 20)); + + v.add(v2); + + LayoutH h = new LayoutH(Align.CENTER, Align.CENTER); + h.add(bnSave = new Button(SAVE, "Save")); + h.add(new Gap(10, 0)); + h.add(bnCancel = new Button(CANCEL, "Cancel")); + v.add(h); + + frame.add(v); + } + + + @Override + public void actionPerformed(Widget widget) + { + if (widget.id == CANCEL) { + closePanel(); + return; + } + + if (widget.id == SAVE) { + GameConfig.setNewProp(GameConfig.pk_win_fs, ckInitFullscreen.isChecked()); + GameConfig.setNewProp(GameConfig.pk_vsync, ckVsync.isChecked()); + GameConfig.setNewProp(GameConfig.pk_win_resize, ckResizable.isChecked()); + + GameConfig.setNewProp(GameConfig.pk_music_volume, Math.round(sliderMusic.getValue() * 100)); + GameConfig.setNewProp(GameConfig.pk_sound_volume, Math.round(sliderSound.getValue() * 100)); + + GameConfig.saveLoad(); + GameConfig.useLoaded(); + + // TODO from config + SoundManager.volumeGui.set(1f); + SoundManager.volumeEffects.set(1f); + SoundManager.volumeWater.set(1f); + SoundManager.volumeAmbients.set(1f); + + Display.setResizable(GameConfig.enableResize); + + closePanel(); + return; + } + + } + + + @Override + public void onKey(int key, char c, boolean down) + { + super.onKey(key, c, down); + + if (key == Keyboard.KEY_ESCAPE && down) { + actionPerformed(bnCancel); + } + + if (key == Keyboard.KEY_RETURN && down) { + actionPerformed(bnSave); + } + } + + + @Override + public void onFocus() + { + Keyboard.enableRepeatEvents(true); + } + + + @Override + public void onBlur() + { + Keyboard.enableRepeatEvents(false); + } + + + @Override + public boolean hasBackgroundLayer() + { + return true; + } + + + @Override + public RGB getBackgroundColor() + { + return new RGB(0, 0.4); + } + +} diff --git a/src/net/tortuga/gui/panels/PanelEmpty.java b/src/net/tortuga/gui/panels/PanelEmpty.java new file mode 100644 index 0000000..e4a0501 --- /dev/null +++ b/src/net/tortuga/gui/panels/PanelEmpty.java @@ -0,0 +1,54 @@ +package net.tortuga.gui.panels; + + +import net.tortuga.gui.screens.Screen; + + +/** + * Empty do-nothing panel, used in Screens with no Gui instead of NULL. + * + * @author MightyPork + */ +public class PanelEmpty extends Panel { + + /** + * New empty panel + * + * @param screen screen + */ + public PanelEmpty(Screen screen) { + super(screen); + } + + + @Override + public void onCreate() + {} + + + @Override + public void onClose() + {} + + + @Override + public boolean hasBackgroundLayer() + { + return false; + } + + + @Override + public void updateGui() + {} + + + @Override + protected void renderPanel() + {} + + + @Override + public void onWindowChanged() + {} +} diff --git a/src/net/tortuga/gui/panels/PanelGame.java b/src/net/tortuga/gui/panels/PanelGame.java new file mode 100644 index 0000000..bf8053f --- /dev/null +++ b/src/net/tortuga/gui/panels/PanelGame.java @@ -0,0 +1,607 @@ +package net.tortuga.gui.panels; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.tortuga.fonts.Fonts; +import net.tortuga.gui.screens.ScreenGame; +import net.tortuga.gui.screens.ScreenMenuMain; +import net.tortuga.gui.widgets.ETheme; +import net.tortuga.gui.widgets.IWidgetFactory; +import net.tortuga.gui.widgets.Theme; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.gui.widgets.composite.*; +import net.tortuga.gui.widgets.display.Text; +import net.tortuga.gui.widgets.input.Button; +import net.tortuga.gui.widgets.input.ButtonIcon; +import net.tortuga.gui.widgets.layout.FullWidthLayout; +import net.tortuga.gui.widgets.layout.LayoutH; +import net.tortuga.gui.widgets.layout.LayoutV; +import net.tortuga.gui.widgets.layout.frame.FrameBelt; +import net.tortuga.gui.widgets.layout.frame.FrameBottom; +import net.tortuga.gui.widgets.layout.frame.FrameTop; +import net.tortuga.level.LevelBundle; +import net.tortuga.level.program.GrainRegistry; +import net.tortuga.level.program.StoneRegistry; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.ProgTileStone; +import net.tortuga.level.program.tiles.grains.GrainJumpLabel; +import net.tortuga.level.program.tiles.grains.GrainNumber; +import net.tortuga.level.program.tiles.grains.GrainVariable; +import net.tortuga.level.program.tiles.stones.StoneLabel; +import net.tortuga.textures.Tx; +import net.tortuga.util.Align; + +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.math.Calc; + + +/** + * Game panel + * + * @author MightyPork + */ +public class PanelGame extends PanelGui { + + @SuppressWarnings("unused") + private static final int PG_INFO = 0, PG_PROG = 1, PG_MAP = 2; + + private static final int id_BACK = 0, id_RUN = 1, id_PROGRAM = 2, id_TURTLE = 3; + + private static final int SHOP_ROW = 12; + public static final int LOW_PANEL_SIZE = 60; + public static final int TOP_PANEL_SIZE = 60; + + /** Game GUI page: 0 = info, 1 = program, 2 = turtle map */ + private int page = PG_PROG; + + private Button bnProg; + + private Button bnTurtle; + + private static BeltCellFactory beltCellFactory = new BeltCellFactory(); + private List beltCells = new ArrayList(); + + private static LowerShopSlotFactory stoneShopSlotFactory = new LowerShopSlotFactory(); + private List stoneShopSlots = new ArrayList(); + + private static LowerShopRowFactory lowerShopRowFactory = new LowerShopRowFactory(); + private List lowerShopRows = new ArrayList(); + + private static UpperShopSlotFactory grainShopSlotFactory = new UpperShopSlotFactory(); + private List grainShopSlots = new ArrayList(); + + private static UpperShopRowFactory upperShopRowFactory = new UpperShopRowFactory(); + private List upperShopRows = new ArrayList(); + + // the whole programming layout + private LayoutV programmingLayout; + private LayoutV ingameLayout; + + // lower shop box + private CompositeScrollBoxV lowerShopBox; + // upper shop box + private CompositeScrollBoxV upperShopBox; + + // belt frame + private FrameBelt beltFrame; + + // belt box + private CompositeScrollBoxH beltBox; + + private FullWidthLayout beltLayout; + + private DragDropServer dndServer; + + private LevelBundle bundle; + + /** Screen as ScreenGame */ + private ScreenGame screenGame; + + private static class BeltCellFactory implements IWidgetFactory { + + @Override + public Widget getWidget() + { + return new PgmBeltCell(); + } + + + public Widget getWidget(int i) + { + return new PgmBeltCell(i); + } + + } + + private static class LowerShopSlotFactory implements IWidgetFactory { + + @Override + public Widget getWidget() + { + return new PgmShopSquare(); + } + + + public Widget getWidget(ProgTileStone stone) + { + return new PgmShopSquare(stone); + } + } + + private static class UpperShopSlotFactory implements IWidgetFactory { + + @Override + public Widget getWidget() + { + return new PgmShopHexagon(); + } + + + public Widget getWidget(ProgTileGrain stone) + { + return new PgmShopHexagon(stone); + } + } + + private static class LowerShopRowFactory implements IWidgetFactory { + + @Override + public LayoutH getWidget() + { + return getWidget(SHOP_ROW); + } + + + private LayoutH getWidget(int inRow) + { + LayoutH lh = new LayoutH(); + for (int j = 0; j < inRow; j++) { + lh.add(stoneShopSlotFactory.getWidget()); + } + + return lh; + } + + + private LayoutH getWidget(List childs) + { + LayoutH lh = new LayoutH(); + + for (Widget w : childs) { + lh.add(w); + } + + return lh; + } + } + + private static class UpperShopRowFactory implements IWidgetFactory { + + @Override + public LayoutH getWidget() + { + return getWidget(SHOP_ROW); + } + + + private LayoutH getWidget(int inRow) + { + LayoutH lh = new LayoutH(); + for (int j = 0; j < inRow; j++) { + lh.add(grainShopSlotFactory.getWidget()); + } + + return lh; + } + + + private LayoutH getWidget(List childs) + { + LayoutH lh = new LayoutH(); + + for (Widget w : childs) { + lh.add(w); + } + + return lh; + } + } + + + @Override + public boolean hasBackgroundLayer() + { + return page == PG_PROG; + } + + + @Override + public RGB getBackgroundColor() + { + return new RGB(0, 0.5); + } + + + /** + * Game panel + * + * @param screen the screen + * @param bundle the level bundle + */ + public PanelGame(ScreenGame screen, LevelBundle bundle) { + super(screen); + this.bundle = bundle; + + screen.setMap(bundle.getBuiltMap().copy()); + + this.screenGame = screen; + } + + + @Override + public void initGui() + { + FullWidthLayout fwl; + FrameTop ft; + FrameBottom fb; + LayoutH h; + + // sort the tile lists + Collections.sort(bundle.progGrains); + Collections.sort(bundle.progStones); + + int programLength = bundle.progLength; + int labels = bundle.progLabels; + int vars = bundle.progVars; + + List grains = new ArrayList(); + List stones = new ArrayList(); + + // GRAINS + + // add labels (stones and grains in one cycle, to save time) + for (int i = 0; i < labels; i++) { + stones.add((ProgTileStone) new StoneLabel().setVariant(i)); + grains.add((ProgTileGrain) new GrainJumpLabel().setVariant(i)); + } + + int grainNumberId = GrainRegistry.getGrainIndex(GrainNumber.class); + int grainLabelId = GrainRegistry.getGrainIndex(GrainJumpLabel.class); + int stoneLabelId = StoneRegistry.getStoneIndex(StoneLabel.class); + + // add requested grains + for (int id : bundle.progGrains) { + if (id == grainNumberId || id == grainLabelId || id < 1000) continue; + grains.add(GrainRegistry.getGrain(id)); + } + + // if there are variables, make sure there's also a number grain + if (bundle.progVars > 0 || bundle.progStones.contains(grainNumberId)) { + grains.add(new GrainNumber()); + } + + // add variables + for (int i = 0; i < vars; i++) { + grains.add((ProgTileGrain) new GrainVariable().setVariant(i)); + } + + // STONES + + // add the stones + for (int id : bundle.progStones) { + if (id == stoneLabelId || id > 1000) continue; + stones.add(StoneRegistry.getStone(id)); + } + + dndServer = new DragDropServer(); + + // build belt + for (int i = 0; i < programLength; i++) { + Widget widget = beltCellFactory.getWidget(i + 1); + beltCells.add(widget); + ((PgmBeltCell) widget).setServer(dndServer); + } + + // prepare shop cells + for (ProgTileStone stone : stones) { + Widget widget = stoneShopSlotFactory.getWidget(stone); + stoneShopSlots.add(widget); + ((PgmShopBase) widget).setServer(dndServer); + } + + for (ProgTileGrain grain : grains) { + Widget widget = grainShopSlotFactory.getWidget(grain); + grainShopSlots.add(widget); + ((PgmShopBase) widget).setServer(dndServer); + } + + if (stoneShopSlots.size() == 0) stoneShopSlots.add(stoneShopSlotFactory.getWidget()); + if (grainShopSlots.size() == 0) grainShopSlots.add(grainShopSlotFactory.getWidget()); + + // put shop cells to shop + for (int i = 0; i < Math.ceil(stoneShopSlots.size() / ((double) SHOP_ROW)); i++) { + int begin = i * SHOP_ROW; + int end = (i + 1) * SHOP_ROW; + + end = Calc.clampi(end, 0, stoneShopSlots.size()); + + List row = stoneShopSlots.subList(begin, end); + + while (row.size() < SHOP_ROW) { + Widget widget = stoneShopSlotFactory.getWidget(); + row.add(widget); + ((PgmShopBase) widget).setServer(dndServer); + } + + lowerShopRows.add(lowerShopRowFactory.getWidget(row)); + } + + for (int i = 0; i < Math.ceil(grainShopSlots.size() / ((double) SHOP_ROW)); i++) { + int begin = i * SHOP_ROW; + int end = (i + 1) * SHOP_ROW; + + end = Calc.clampi(end, 0, grainShopSlots.size()); + + List row = grainShopSlots.subList(begin, end); + + while (row.size() < SHOP_ROW) { + Widget widget = grainShopSlotFactory.getWidget(); + row.add(widget); + ((PgmShopBase) widget).setServer(dndServer); + } + + upperShopRows.add(upperShopRowFactory.getWidget(row)); + } + + //@formatter:off + + // top frame with title + Text title = Theme.mkTitle("Game Screen of Tortuga"); + ft = new FrameTop(title, Align.CENTER, Align.CENTER); + ft.setMinHeight(TOP_PANEL_SIZE); + fwl = new FullWidthLayout(ft, Align.CENTER, Align.CENTER); + addGui(fwl, Align.CENTER, Align.TOP); + + // left button + h = new LayoutH(Align.LEFT, Align.CENTER); + h.setMargins(10, 0, 0, 0).setMinHeight(TOP_PANEL_SIZE); + + ButtonIcon bnQuit = new ButtonIcon(id_BACK, Tx.ICON_QUIT, new RGB(0x990000)); + bnQuit.setTooltip("Leave Game", new RGB(0xcc0000)); + h.add(bnQuit); + addGui(h, Align.LEFT, Align.TOP); + + + // right buttons + bnProg = new ButtonIcon(id_PROGRAM, Tx.ICON_CODE, new RGB(0xffff00)); + bnProg.setTooltip("Program", new RGB(0xcccc00)); + bnProg.setSelected(page == PG_PROG); + + bnTurtle = new ButtonIcon(id_TURTLE, Tx.ICON_TURTLE, new RGB(0x00ff00)); + bnTurtle.setTooltip("Level Map", new RGB(0x00cc00)); + bnTurtle.setSelected(page == PG_MAP); + + h = new LayoutH(Align.RIGHT, Align.CENTER); + h.setMargins(0, 0, 10, 0).setMinHeight(TOP_PANEL_SIZE); + h.add(bnProg); + h.add(bnTurtle); + + addGui(h, Align.RIGHT, Align.TOP); + + + + + + // center layout + programmingLayout = new LayoutV(Align.CENTER, Align.CENTER); + + upperShopBox = new CompositeScrollBoxV(2, upperShopRowFactory, upperShopRows, Align.CENTER, Align.BOTTOM); + upperShopBox.setMarginsV(10, 0); + fwl = new FullWidthLayout(upperShopBox, Align.CENTER, Align.CENTER); + programmingLayout.add(fwl); + + beltBox = new CompositeScrollBoxH(5, beltCellFactory, beltCells, Align.CENTER, Align.CENTER); + beltFrame = new FrameBelt(beltBox, Align.CENTER, Align.TOP); + beltFrame.padding.top += 7; + beltFrame.padding.bottom -= 3; + beltLayout = new FullWidthLayout(beltFrame, Align.CENTER, Align.CENTER); + beltLayout.setRenderIndex(1); + beltLayout.setMarginsV(10, 10); + programmingLayout.add(beltLayout); + + lowerShopBox = new CompositeScrollBoxV(2, lowerShopRowFactory, lowerShopRows, Align.CENTER, Align.TOP); + fwl = new FullWidthLayout(lowerShopBox, Align.CENTER, Align.CENTER); + programmingLayout.add(fwl); + programmingLayout.add(dndServer); + addGui(programmingLayout, Align.CENTER, Align.CENTER).setRenderIndex(1); + + + // MAP + + ingameLayout = new LayoutV(Align.CENTER, Align.CENTER); + + addGui(ingameLayout, Align.CENTER, Align.CENTER); + + // BOTTOM GUI + Button bn = new Button(id_RUN, "Go turtle, go!", Fonts.gui_title); + bn.setTheme(ETheme.BUTTON_LAUNCH); + fb = new FrameBottom(bn, Align.CENTER, Align.CENTER); + fb.setMinHeight(LOW_PANEL_SIZE); + fwl = new FullWidthLayout(fb, Align.CENTER, Align.CENTER); + addGui(fwl, Align.CENTER, Align.BOTTOM); + + //@formatter:on + } + + + @Override + public void onPostInit() + { + onWindowChanged(); // adapt belt to window + } + + + @Override + public void onWindowChanged() + { + super.onWindowChanged(); + + adaptBeltSize(); + adaptShopSizes(); + + updateWidgetPositions(); + + screenGame.correctMapOffset(); + } + + + private void adaptBeltSize() + { + int width = (int) (app.getSize().x); + + Widget b = beltCellFactory.getWidget(); + b.calcChildSizes(); + int one = b.getSize().xi() + b.getMargins().left; + + int canFit = width / one; + + beltBox.adaptForSize(canFit); + } + + + private void adaptShopSizes() + { + int height = (int) (app.getSize().y); + + height -= LOW_PANEL_SIZE; + height -= TOP_PANEL_SIZE; + height -= beltLayout.getSize().y; + height -= beltLayout.getMargins().getVetical(); + height -= lowerShopBox.getMargins().getVetical(); + height -= upperShopBox.getMargins().getVetical(); + height -= 8; + + height /= 2; + + Widget b = lowerShopRowFactory.getWidget(); + b.calcChildSizes(); + int one = b.getSize().yi() + b.getMargins().top; + + int canFit = (int) Math.floor(height / one); + + lowerShopBox.adaptForSize(canFit); + + upperShopBox.adaptForSize(canFit); + } + + + @Override + public void actionPerformed(Widget widget) + { + switch (widget.id) { + case id_BACK: + app.replaceScreen(new ScreenMenuMain()); + break; + + case id_PROGRAM: + bnProg.setSelected(true); + bnTurtle.setSelected(false); + programmingLayout.setVisible(true); + page = PG_PROG; + break; + + case id_TURTLE: + bnProg.setSelected(false); + bnTurtle.setSelected(true); + programmingLayout.setVisible(false); + page = PG_MAP; + break; + } + } + + private boolean dragging = false; + private Coord dragStartMapOffset = null; + private Coord dragStartMouse = null; + + + @Override + public void onMouseButton(int button, boolean down, int wheelDelta, Coord pos, Coord deltaPos) + { + super.onMouseButton(button, down, wheelDelta, pos, deltaPos); + + if (page != 2) return; + + Coord mouse = new Coord(Mouse.getX(), Mouse.getY()); + Coord scrSize = app.getSize(); + + if (mouse.y < scrSize.y - TOP_PANEL_SIZE && mouse.y > LOW_PANEL_SIZE) { + if (button == 0 && down) { + dragging = true; + dragStartMapOffset = screenGame.mapOffset; + dragStartMouse = mouse; + } + } + + if (button == 0 && !down) { + dragging = false; + } + + } + + + @Override + public void onKey(int key, char c, boolean down) + { + if (down && key == Keyboard.KEY_R) { + screenGame.setMap(bundle.getBuiltMap().copy()); + } + } + + + @Override + public void handleStaticInputs() + { + super.handleStaticInputs(); + + if (screenGame.currentMap.isMovementFinished()) { + if (Keyboard.isKeyDown(Keyboard.KEY_UP)) { + screenGame.currentMap.getTurtleController().goForward(); + + } else if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) { + screenGame.currentMap.getTurtleController().goBackward(); + + } else if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) { + screenGame.currentMap.getTurtleController().turnLeft(); + + } else if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) { + screenGame.currentMap.getTurtleController().turnRight(); + + } + } + + if (dragging) { + Coord mouse = new Coord(Mouse.getX(), Mouse.getY()); + + screenGame.mapOffset = dragStartMapOffset.add(dragStartMouse.vecTo(mouse)); + + Coord oldOffset = screenGame.mapOffset.copy(); + + screenGame.correctMapOffset(); + + if (!oldOffset.equals(screenGame.mapOffset)) { + dragStartMapOffset = screenGame.mapOffset.copy(); + dragStartMouse = mouse.copy(); + } + + } + + } + +} diff --git a/src/net/tortuga/gui/panels/PanelGui.java b/src/net/tortuga/gui/panels/PanelGui.java new file mode 100644 index 0000000..b0cbd8e --- /dev/null +++ b/src/net/tortuga/gui/panels/PanelGui.java @@ -0,0 +1,158 @@ +package net.tortuga.gui.panels; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.tortuga.annotations.Unimplemented; +import net.tortuga.gui.screens.Screen; +import net.tortuga.gui.widgets.GuiRoot; +import net.tortuga.gui.widgets.GuiRoot.EventListener; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.gui.widgets.layout.LayoutV; +import net.tortuga.util.Align; + +import com.porcupine.coord.Coord; +import com.porcupine.math.Calc; + + +public abstract class PanelGui extends Panel implements EventListener { + + protected List guis = new ArrayList(); + private LayoutV v; + + + public PanelGui(Screen screen) { + super(screen); + } + + + public GuiRoot addGui(Widget layout, int alignH, int alignV) + { + GuiRoot gui = new GuiRoot(this, layout); + gui.setAlign(alignH, alignV); + guis.add(gui); + return gui; + } + + + public GuiRoot addGui(Widget layout) + { + GuiRoot gui = new GuiRoot(this, layout); + gui.setAlign(Align.CENTER, Align.CENTER); + guis.add(gui); + return gui; + } + + + @Override + public final void onCreate() + { + initGui(); + + for (GuiRoot gui : guis) { + gui.setParentPanel(this); + gui.updatePositions(); + } + + Collections.sort(guis); + onPostInit(); + } + + + /** + * Called after panel is fully created + */ + @Unimplemented + public void onPostInit() + {} + + + @Override + @Unimplemented + public void onClose() + {} + + + public final void updateWidgetPositions() + { + for (GuiRoot gui : guis) { + gui.updatePositions(); + } + } + + +// public final GuiRoot getRootWidget() { +// return gui; +// } + + public abstract void initGui(); + + + @Override + public void onWindowChanged() + { + for (GuiRoot gui : guis) { + gui.updatePositions(); + } + } + + + @Override + public boolean hasBackgroundLayer() + { + return true; + } + + + @Override + @Unimplemented + public void updateGui() + {} + + + @Override + protected void renderPanel() + { + //glPushMatrix(); + for (GuiRoot gui : guis) { + gui.render(); + //glTranslated(0, 0, 10); + } + //glPopMatrix(); + } + + + @Override + public abstract void actionPerformed(Widget widget); + + + @Override + public void onMouseButton(int button, boolean down, int wheelDelta, Coord pos, Coord deltaPos) + { + for (GuiRoot gui : guis) { + if (button != -1) gui.onMouseButton(button, down); + if (wheelDelta != 0) gui.onScroll(Calc.clampi(wheelDelta, -1, 1)); + } + } + + + @Override + public void onKey(int key, char c, boolean down) + { + for (GuiRoot gui : guis) { + gui.onKeyDown(key, c, down); + } + } + + + @Override + public void handleStaticInputs() + { + for (GuiRoot gui : guis) { + gui.handleStaticInputs(); + } + } + +} diff --git a/src/net/tortuga/gui/panels/PanelMenu.java b/src/net/tortuga/gui/panels/PanelMenu.java new file mode 100644 index 0000000..78b21fa --- /dev/null +++ b/src/net/tortuga/gui/panels/PanelMenu.java @@ -0,0 +1,148 @@ +package net.tortuga.gui.panels; + + +import net.tortuga.App; +import net.tortuga.gui.panels.dialogs.PanelDialogModal; +import net.tortuga.gui.panels.dialogs.PanelDialogModal.IDialogListener; +import net.tortuga.gui.screens.Screen; +import net.tortuga.gui.screens.ScreenGame; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.gui.widgets.display.Image; +import net.tortuga.gui.widgets.layout.Gap; +import net.tortuga.gui.widgets.layout.LayoutV; +import net.tortuga.gui.widgets.menu.MenuButton; +import net.tortuga.textures.Tx; + + +/** + * Main menu panel + * + * @author MightyPork + */ +public class PanelMenu extends PanelGui { + + private static final int EXIT = 0; + private static final int PLAY = 1; + private static final int CONFIG = 3; + private static final int USERS = 4; + private static final int LEVELS = 5; + + +// private LayoutH top; +// private FrameWindow frame; + + public PanelMenu(Screen screen) { + super(screen); + } + + + @Override + public void initGui() + { + LayoutV v = new LayoutV(); + + v.add(new Image(Tx.TITLE, 0)); + //v.add(new MenuTitle("Tortuga")); + v.add(new Gap(0, 10)); + v.add(new MenuButton(LEVELS, "Show Game Screen")); + //v.add(new Gap(0, 15)); + v.add(new MenuButton(CONFIG, "Settings")); + v.add(new MenuButton(EXIT, "Quit Game")); + + addGui(v); + +// top = new LayoutH(Align.CENTER, Align.CENTER); +// top.setMargins(0, 0, 0, 0); +// +// frame = new FrameWindow(top); +// frame.setMinHeight(80); +// frame.setMarginsH(50, 50); +// +// addGui(new FullWidthLayout(frame), Align.CENTER, Align.TOP); + + } + + +// @Override +// public void onWindowChanged() { +// super.onWindowChanged(); +// +// top.removeAll(); +// int width = (int) (app.getSize().x - frame.getMargins().getHorizontal() - frame.paddingLeft - frame.paddingRight); +// +// IWidgetFactory maker = new IWidgetFactory() { +// @Override +// public Widget getWidget() { +// return new Button(-1, "Hello!"); +// } +// }; +// +// Widget b = maker.getWidget(); +// b.calcChildSizes(); +// int one = b.getSize().xi()+b.getMargins().left; +// +// int canFit = width / one; +// +// for(int i=0; i + * Screen animates 3D world, while contained panels render 2D overlays, process + * inputs and run the game logic. + * + * @author MightyPork + */ +public abstract class Screen implements IRenderer { + + /** application instance, for easier calls */ + public App app = App.inst; + + /** root panel */ + public Panel rootPanel = new PanelEmpty(this); + + /** RNG */ + public Random rand = new Random(); + + + /** + * Set screen root panel + * + * @param panel root panel + */ + public final void setRootPanel(Panel panel) + { + this.rootPanel = panel; + } + + + /** + * handle fullscreen change + */ + @Override + public final void onFullscreenChange() + { + onWindowResize(); + rootPanel.onViewportChanged(); + } + + + /** + * handle window resize. + */ + public void onWindowResize() + { + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + Coord s = app.getSize(); + + glViewport(0, 0, s.xi(), s.yi()); + + glOrtho(0, s.x, 0, s.y, -1000, 1000); + + glMatrixMode(GL_MODELVIEW); + + glLoadIdentity(); + + glEnable(GL_BLEND); + //glDisable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_TEXTURE_2D); + + rootPanel.onViewportChanged(); + } + + + /** + * Initialize screen + */ + public void init() + { + onWindowResize(); + + initScreen(); + + rootPanel.onCreate(); + rootPanel.onFocus(); + + // SETUP LIGHTS + + glDisable(GL_LIGHTING); + + // OTHER SETTINGS + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glClearDepth(1f); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + + glEnable(GL_NORMALIZE); + + glShadeModel(GL_SMOOTH); + glDisable(GL_TEXTURE_2D); + + } + + + /** + * Here you can initialize the screen. + */ + public abstract void initScreen(); + + + /** + * Update tick + */ + @Override + public final void updateGui() + { + Mouse.poll(); + Keyboard.poll(); + checkInputEvents(); + + getFocusedPanel().updateGui(); + + onGuiUpdate(); + + app.weatherAnimation.updateGui(); + } + + + @Unimplemented + protected void onGuiUpdate() + {} + + + /** + * Render screen + * + * @param delta delta time (position between two update ticks, to allow + * super-smooth animations) + */ + @Override + public final void render(double delta) + { + glPushAttrib(GL_ENABLE_BIT); + + // draw the directly rendered 3D stuff + rootPanel.render3D(); + + rootPanel.render(delta); + + glPopAttrib(); + } + + + /** + * @return topmost panel which can handle inputs + */ + protected final Panel getFocusedPanel() + { + return rootPanel.getTop(); + } + + + /** + * Check input events and process them. + */ + private final void checkInputEvents() + { + while (Keyboard.next()) { + int key = Keyboard.getEventKey(); + boolean down = Keyboard.getEventKeyState(); + char c = Keyboard.getEventCharacter(); + Keys.onKey(key, down); + getFocusedPanel().onKey(key, c, down); + } + while (Mouse.next()) { + int button = Mouse.getEventButton(); + boolean down = Mouse.getEventButtonState(); + Coord delta = new Coord(Mouse.getEventDX(), Mouse.getEventDY()); + Coord pos = new Coord(Mouse.getEventX(), Mouse.getEventY()); + int wheeld = Mouse.getEventDWheel(); + + getFocusedPanel().onMouseButton(button, down, wheeld, pos, delta); + } + + int xc = Mouse.getX(); + int yc = Mouse.getY(); + int xd = Mouse.getDX(); + int yd = Mouse.getDY(); + int wd = Mouse.getDWheel(); + + if (Math.abs(xd) > 0 || Math.abs(yd) > 0 || Math.abs(wd) > 0) { + getFocusedPanel().onMouseMove(new Coord(xc, yc), new Vec(xd, yd), wd); + } + + getFocusedPanel().handleStaticInputs(); + } + + + /** + * Render background 2D (all is ready for rendering) + * + * @param delta delta time + */ + @Unimplemented + public void render2D(double delta) + {} + + + /** + * Get if ambient animation is enabled for this screen + * + * @return + */ + public boolean isWeatherEnabled() + { + return true; + } +} diff --git a/src/net/tortuga/gui/screens/ScreenGame.java b/src/net/tortuga/gui/screens/ScreenGame.java new file mode 100644 index 0000000..8510847 --- /dev/null +++ b/src/net/tortuga/gui/screens/ScreenGame.java @@ -0,0 +1,251 @@ +package net.tortuga.gui.screens; + + +import net.tortuga.animations.WaterAnimator; +import net.tortuga.gui.panels.PanelGame; +import net.tortuga.level.GameState; +import net.tortuga.level.LevelBundle; +import net.tortuga.level.map.EntityDescriptorList; +import net.tortuga.level.map.TileGridBuilder; +import net.tortuga.level.map.TurtleMap; +import net.tortuga.level.map.TurtleMapDescriptor; +import net.tortuga.level.map.entities.EntityDescriptor; +import net.tortuga.level.map.entities.TurtleDescriptor; +import net.tortuga.sounds.Loops; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.coord.Rect; + + +/** + * main menu screen + * + * @author MightyPork + */ +public class ScreenGame extends Screen { + + public TurtleMap currentMap = null; + public Coord mapOffset = new Coord(); + private WaterAnimator wa = new WaterAnimator(); + + + @Override + public boolean isWeatherEnabled() + { + return true; + } + + + @Override + public void initScreen() + { + Loops.playIngame(); + + LevelBundle level = new LevelBundle(); + + level.progGrains.add(1001); // jump start + level.progGrains.add(1102); + level.progGrains.add(1103); + level.progGrains.add(1104); + level.progGrains.add(1105); + level.progGrains.add(1106); + + level.progStones.add(1); + level.progStones.add(2); + level.progStones.add(3); + level.progStones.add(4); + + level.progStones.add(101); + level.progStones.add(102); + level.progStones.add(103); + level.progStones.add(104); + level.progStones.add(105); + + level.progStones.add(202); + level.progStones.add(203); + level.progStones.add(204); + + level.progStones.add(301); + level.progStones.add(302); + + level.progStones.add(401); + level.progStones.add(402); + level.progStones.add(403); + + level.progStones.add(501); + level.progStones.add(502); + level.progStones.add(503); + level.progStones.add(504); + level.progStones.add(505); + level.progStones.add(506); + level.progStones.add(507); + level.progStones.add(508); + level.progStones.add(509); + level.progStones.add(510); + level.progStones.add(511); + level.progStones.add(512); + level.progStones.add(513); + + level.progLabels = 5; + level.progVars = 2; + level.progLength = 16; + + // TODO load map from a TurtleMapDescriptor + +// BUILD A LEVEL DESCRIPTOR + int w = 12, h = 6, d = 4; + // x,y,z, ID + TileGridBuilder builder = new TileGridBuilder(w, h, d); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + builder.setTile(x, y, 0, 1); // stone + } + } + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + builder.setTile(x, y, 1, 2); // grass + } + } + + // crates + builder.setTile(2, 2, 2, 200); + builder.setTile(0, 2, 2, 200); + + // ice + builder.setTile(3, 4, 1, 5); + builder.setTile(4, 4, 1, 5); + builder.setTile(5, 4, 1, 5); + builder.setTile(6, 4, 1, 5); + builder.setTile(7, 4, 1, 5); + builder.setTile(6, 3, 1, 5); + builder.setTile(6, 2, 1, 5); + + builder.setTile(9, 4, 1, 0); // hole + + builder.setTile(1, 1, 2, 200); + + builder.setTile(5, 2, 2, 201); // elev + builder.setTile(3, 2, 2, 201); // elev + builder.setTile(6, 2, 2, 200); + + builder.setTile(9, 2, 2, 202); // switches + builder.setTile(10, 2, 2, 203); + builder.setTile(11, 2, 2, 203); + + TurtleDescriptor turtle = new TurtleDescriptor(0, new CoordI(7, 2, 2)); + CoordI goal = new CoordI(8, 4, 2); + boolean hasGoal = true; + + EntityDescriptorList entities = new EntityDescriptorList(); + // ID, x,y,z + entities.add(new EntityDescriptor(4, new CoordI(1, 1, 3))); + + entities.add(new EntityDescriptor(4, new CoordI(7, 2, 2))); + entities.add(new EntityDescriptor(2, new CoordI(8, 2, 2))); + entities.add(new EntityDescriptor(3, new CoordI(9, 2, 2))); + entities.add(new EntityDescriptor(1, new CoordI(10, 2, 2))); + entities.add(new EntityDescriptor(5, new CoordI(11, 2, 2))); + + entities.add(new EntityDescriptor(6, new CoordI(7, 3, 2))); + entities.add(new EntityDescriptor(7, new CoordI(8, 3, 2))); + entities.add(new EntityDescriptor(8, new CoordI(9, 3, 2))); + entities.add(new EntityDescriptor(9, new CoordI(10, 3, 2))); + entities.add(new EntityDescriptor(10, new CoordI(11, 3, 2))); + + entities.add(new EntityDescriptor(11, new CoordI(11, 4, 2))); + + level.mapDescriptor = new TurtleMapDescriptor(turtle, goal, hasGoal, builder.toIdGrid(), entities); + +// BUILD MAP FROM DESCRIPTOR + level.buildMap(); + + setRootPanel(new PanelGame(this, level)); + } + + + /** + * Set current map. + * + * @param map map + */ + public void setMap(TurtleMap map) + { + this.currentMap = map; + map.setTurtleTheme(GameState.turtleTheme); + } + + + @Override + public void render2D(double delta) + { + wa.offsetPlus = mapOffset; + wa.render(delta); + + CoordI mapSize = currentMap.getSize(); + Coord renderSize = new Coord(mapSize.x * 64, mapSize.y * 64 + mapSize.z * 32); + Coord screenSize = app.getSize(); + + Coord min = screenSize.mul(0.5); + min.sub_ip(renderSize.x / 2, renderSize.y / 2); + min.add_ip(mapOffset); + min.round_ip(); + + currentMap.render(min); + } + + + /** + * Correct map offset (after screen resize) + */ + public void correctMapOffset() + { + CoordI mapSize = currentMap.getSize(); + Coord renderSize = new Coord(mapSize.x * 64, mapSize.y * 64 + mapSize.z * 32); + Coord screenSize = app.getSize(); + Coord min = screenSize.div(2).sub_ip(renderSize.x / 2, renderSize.y / 2).add_ip(mapOffset).round_ip(); + + Rect rect = Rect.fromSize(min, renderSize); + Rect disp = new Rect(screenSize); + disp.growDown_ip(-PanelGame.LOW_PANEL_SIZE); + disp.growUp_ip(-PanelGame.TOP_PANEL_SIZE); + + boolean xenough = false, yenough = false; + if (rect.getSize().y <= disp.getSize().y) { + mapOffset.y = 0; + yenough = true; + } + + if (rect.getSize().x <= disp.getSize().x) { + mapOffset.x = 0; + xenough = true; + } + + if (!xenough && rect.x1() > disp.x1() + 32) { + mapOffset.x -= rect.x1() - (disp.x1() + 32); + } + + if (!xenough && rect.x2() < disp.x2() - 32) { + mapOffset.x += (disp.x2()) - (rect.x2() + 32); + } + + if (!yenough && rect.y1() > disp.y1() + 32) { + mapOffset.y -= rect.y1() - (disp.y1() + 32); + } + + if (!yenough && rect.y2() < disp.y2() - 32) { + mapOffset.y += (disp.y2()) - (rect.y2() + 32); + } + + } + + + @Override + protected void onGuiUpdate() + { + super.onGuiUpdate(); + wa.updateGui(); + } + +} diff --git a/src/net/tortuga/gui/screens/ScreenMenuMain.java b/src/net/tortuga/gui/screens/ScreenMenuMain.java new file mode 100644 index 0000000..8eb91cd --- /dev/null +++ b/src/net/tortuga/gui/screens/ScreenMenuMain.java @@ -0,0 +1,37 @@ +package net.tortuga.gui.screens; + + +import net.tortuga.gui.panels.PanelMenu; +import net.tortuga.sounds.Loops; +import net.tortuga.textures.Textures; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Rect; + + +/** + * main menu screen + * + * @author MightyPork + */ +public class ScreenMenuMain extends Screen { + + @Override + public void initScreen() + { + Loops.playMenu(); + + setRootPanel(new PanelMenu(this)); + } + + + @Override + public void render2D(double delta) + { + Rect quad = new Rect(app.getSize()); + Rect quadT = new Rect(app.getSize()); + + RenderUtils.quadTextured(quad, quadT, Textures.steel_big_dk, new RGB(0xdddddd)); + } +} diff --git a/src/net/tortuga/gui/screens/ScreenSplash.java b/src/net/tortuga/gui/screens/ScreenSplash.java new file mode 100644 index 0000000..e6a7d2d --- /dev/null +++ b/src/net/tortuga/gui/screens/ScreenSplash.java @@ -0,0 +1,45 @@ +package net.tortuga.gui.screens; + + +import net.tortuga.gui.panels.PanelSplash; +import net.tortuga.sounds.Loops; +import net.tortuga.textures.Textures; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Rect; + + +/** + * Splash screen + * + * @author MightyPork + */ +public class ScreenSplash extends Screen { + + @Override + public void initScreen() + { + Loops.playIntro(); + + setRootPanel(new PanelSplash(this)); + } + + + @Override + public void render2D(double delta) + { + Rect quad = new Rect(0, 0, app.getSize().x, app.getSize().y); + Rect quad2 = new Rect(0, 0, app.getSize().x, app.getSize().y); + + RenderUtils.quadTextured(quad, quad2, Textures.steel_small_dk, new RGB(0x999999)); + } + + + @Override + public boolean isWeatherEnabled() + { + return false; + } + +} diff --git a/src/net/tortuga/gui/widgets/ColorScheme.java b/src/net/tortuga/gui/widgets/ColorScheme.java new file mode 100644 index 0000000..199521f --- /dev/null +++ b/src/net/tortuga/gui/widgets/ColorScheme.java @@ -0,0 +1,221 @@ +package net.tortuga.gui.widgets; + + +import static net.tortuga.gui.widgets.EColorRole.*; + +import java.util.HashMap; +import java.util.Map; + +import com.porcupine.color.RGB; + + +public class ColorScheme { + + public static ColorScheme getForTheme(ETheme theme) + { + switch (theme) { + case WIDGET: + return widget; + + case TEXT: + return text; + + case BUTTON: + return button; + + case BUTTON_LAUNCH: + return buttonLaunch; + + case MENU_BUTTON: + return menuButton; + + case MENU_TITLE: + return menuTitle; + } + + return widget; + } + + private static ColorScheme text; + private static ColorScheme widget; + private static ColorScheme button; + private static ColorScheme buttonLaunch; + private static ColorScheme menuButton; + private static ColorScheme menuTitle; + + + public static void init() + { + ColorScheme cs; + + //@formatter:off + +// menuButton = cs = new ColorScheme(); +// cs.base.put(FG, new RGB(0x0B74E3)); +// cs.base.put(SHADOW, new RGB(0x003185, 0.2)); +// +// cs.hover.put(FG, new RGB(0x02E9FA)); +// cs.hover.put(SHADOW, new RGB(0x00A8FC, 0.05)); + + + // remade + menuButton = cs = new ColorScheme(); + cs.base.put(FG, new RGB(0xd4d4d4)); + cs.base.put(SHADOW, new RGB(0x000000, 0.1)); + + cs.hover.put(FG, new RGB(0xe4e4e4)); + cs.hover.put(SHADOW, new RGB(0x000000, 0.1)); + + cs.clicked.put(FG, new RGB(0x989898)); + cs.clicked.put(SHADOW, new RGB(0x000000, 0.1)); + + + // remade + menuTitle = cs = new ColorScheme(); + cs.base.put(FG, new RGB(0xffffff)); + cs.base.put(SHADOW, new RGB(0xffffff, 0.2)); + + + text = cs = new ColorScheme(); + cs.base.put(FG, new RGB(0x101010)); + cs.base.put(SHADOW, new RGB(0xffffff, 0.1)); + + // remade + widget = cs = new ColorScheme(); + cs.setBase( + new RGB(0x2f2f2f, 1), // bdr + new RGB(0x9f9f9f, 0.8), // bg + new RGB(0x101010, 1) // fg + ); + cs.setHover( + new RGB(0x2f2f2f, 1), // bdr + new RGB(0xbfbfbf, 0.8), // bg + new RGB(0x101010, 1) // fg + ); + cs.setClicked( + new RGB(0x2f2f2f, 1), // bdr + new RGB(0x8f8f8f, 0.8), // bg + new RGB(0x101010, 1) // fg + ); + cs.setSelected( + new RGB(0x2f2f2f, 1), // bdr + new RGB(0xbfbfff, 0.8), // bg + new RGB(0x101010, 1) // fg + ); + + + button = cs = new ColorScheme(); + cs.base.put(FG, new RGB(0xefefef, 1)); + cs.hover.put(FG, new RGB(0xffffff, 1)); + cs.clicked.put(FG, new RGB(0xdfdfdf, 1)); + cs.selected.put(FG, new RGB(0xefefef, 1)); + cs.base.put(SHADOW, new RGB(0, 0.1)); + + + buttonLaunch = cs = new ColorScheme(); + cs.base.put(FG, new RGB(0x33ff33, 1)); + cs.hover.put(FG, new RGB(0x00ff00, 1)); + cs.clicked.put(FG, new RGB(0x00cc00, 1)); + cs.base.put(SHADOW, new RGB(0, 0.1)); + + //@formatter:on + + } + + private Map base = new HashMap(); + private Map hover = new HashMap(); + private Map clicked = new HashMap(); + private Map selected = new HashMap(); + + + // constructor + public ColorScheme() {} + + + public ColorScheme(RGB border, RGB background, RGB foreground) { + setBase(border, background, foreground); + setHover(border, background, foreground); + setClicked(border, background, foreground); + setSelected(border, background, foreground); + } + + + public ColorScheme(RGB border, RGB background) { + setBase(border, background, RGB.BLACK); + setHover(border, background, RGB.BLACK); + setClicked(border, background, RGB.BLACK); + setSelected(border, background, RGB.BLACK); + } + + + public ColorScheme setBase(RGB border, RGB background, RGB foreground) + { + base.put(BDR, border); + base.put(BG, background); + base.put(FG, foreground); + return this; + } + + + public ColorScheme setHover(RGB border, RGB background, RGB foreground) + { + hover.put(BDR, border); + hover.put(BG, background); + hover.put(FG, foreground); + return this; + } + + + public ColorScheme setClicked(RGB border, RGB background, RGB foreground) + { + clicked.put(BDR, border); + clicked.put(BG, background); + clicked.put(FG, foreground); + return this; + } + + + public ColorScheme setSelected(RGB border, RGB background, RGB foreground) + { + selected.put(BDR, border); + selected.put(BG, background); + selected.put(FG, foreground); + return this; + } + + + private RGB fallback(RGB... options) + { + for (RGB color : options) { + if (color != null) return color; + } + System.out.println(base); + return RGB.RED; // error color + } + + + public RGB getColor(EColorRole role, boolean panelActive, boolean enabled, boolean isHover, boolean isClicked, boolean isSelected) + { + if (!enabled) { + if (isSelected) return fallback(selected.get(role), base.get(role)).mulAlpha(0.3); + return base.get(role).mulAlpha(0.3); + } + + if (panelActive) { + if (isClicked) { + if (isSelected) return fallback(selected.get(role), clicked.get(role), hover.get(role), base.get(role)); + return fallback(clicked.get(role), hover.get(role), base.get(role)); + } + + if (isHover) { + if (isSelected) return fallback(selected.get(role), hover.get(role), base.get(role)); + return fallback(hover.get(role), base.get(role)); + } + } + + if (isSelected) return fallback(selected.get(role), base.get(role)); + + return fallback(base.get(role)); + } + +} diff --git a/src/net/tortuga/gui/widgets/EColorRole.java b/src/net/tortuga/gui/widgets/EColorRole.java new file mode 100644 index 0000000..c56a77a --- /dev/null +++ b/src/net/tortuga/gui/widgets/EColorRole.java @@ -0,0 +1,7 @@ +package net.tortuga.gui.widgets; + + +public enum EColorRole +{ + FG, BG, BDR, SHADOW, BDR_INNER; +} diff --git a/src/net/tortuga/gui/widgets/ETheme.java b/src/net/tortuga/gui/widgets/ETheme.java new file mode 100644 index 0000000..96961aa --- /dev/null +++ b/src/net/tortuga/gui/widgets/ETheme.java @@ -0,0 +1,7 @@ +package net.tortuga.gui.widgets; + + +public enum ETheme +{ + WIDGET, MENU_BUTTON, MENU_TITLE, BUTTON, TEXT, BUTTON_LAUNCH; +} diff --git a/src/net/tortuga/gui/widgets/GuiRoot.java b/src/net/tortuga/gui/widgets/GuiRoot.java new file mode 100644 index 0000000..e756f1d --- /dev/null +++ b/src/net/tortuga/gui/widgets/GuiRoot.java @@ -0,0 +1,374 @@ +package net.tortuga.gui.widgets; + + +import static net.tortuga.util.Align.*; +import static org.lwjgl.opengl.GL11.*; + +import java.util.HashSet; +import java.util.Set; + +import net.tortuga.App; +import net.tortuga.fonts.Fonts; +import net.tortuga.fonts.LoadedFont; +import net.tortuga.gui.panels.Panel; +import net.tortuga.gui.widgets.layout.Gap; +import net.tortuga.util.Align; + +import org.lwjgl.input.Mouse; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; + + +/** + * Root widget, container of layouts + * + * @author MightyPork + */ +public class GuiRoot implements Comparable { + + /** + * Widget event listener + * + * @author MightyPork + */ + public static interface EventListener { + + /** + * On action performed - button clicked etc. + * + * @param widget widget responsible for the event. + */ + public void actionPerformed(Widget widget); + } + + public double renderIndex = 0; + + private Set needingRefresh = new HashSet(); + + /** root child */ + public Widget child = new Gap(0, 0); + /** focused widget */ + public Widget focus = null; + /** event listener */ + public EventListener listener = null; + + /** Horizontal align to window */ + public int alignH = CENTER; + + /** Vertical align to window */ + public int alignV = CENTER; + private Panel panel; + + private String tooltipText = ""; + private RGB tooltipColor = RGB.WHITE; + private boolean tooltipShown = false; + + + /** + * Set rendering index + * + * @param index index + * @return this + */ + public GuiRoot setRenderIndex(double index) + { + this.renderIndex = index; + return this; + } + + + public void scheduleRefresh(IRefreshable w) + { + needingRefresh.add(w); + } + + + public void clearTooltip() + { + tooltipShown = false; + } + + + public void setTooltip(String text, RGB color) + { + tooltipText = text.trim(); + tooltipColor = color; + tooltipShown = true; + } + + + /** + * Gui widget root element + * + * @param listener listener - for widget events + * @param child root child (a layout) + */ + public GuiRoot(EventListener listener, Widget child) { + this.listener = listener; + this.child = child; + if (listener instanceof Panel) panel = (Panel) listener; + } + + + public GuiRoot setParentPanel(Panel p) + { + this.panel = p; + return this; + } + + + public boolean isPanelOnTop() + { + return panel.isFocused(); + } + + + /** + * Add primary child + * + * @param layout + */ + public void setRootWidget(Widget layout) + { + this.child = layout; + } + + + /** + * Set alignment relative to window; Use constants from Align class. + * + * @param horizontal horizontal align + * @param vertical vertical align + * @return this + */ + public GuiRoot setAlign(int horizontal, int vertical) + { + alignH = horizontal; + alignV = vertical; + return this; + } + + + /** + * Set alignment relative to window; Use constants from Align class. + * + * @param horizontal horizontal align + * @return this + */ + public GuiRoot setAlignH(int horizontal) + { + alignH = horizontal; + return this; + } + + + /** + * Set alignment relative to window; Use constants from Align class. + * + * @param vertical vertical align + * @return this + */ + public GuiRoot setAlignV(int vertical) + { + alignV = vertical; + return this; + } + + + /** + * Do render - centered on screen. + */ + public void render() + { + clearTooltip(); + + glPushMatrix(); + + Coord lower = getLowerPoint(); + + glTranslated(lower.x, lower.y, 0); + glPushMatrix(); + child.render(getRelativeMousePos()); + glPopMatrix(); + + if (tooltipShown && isPanelOnTop()) { + Coord mouse = getRelativeMousePos(); + LoadedFont font = Fonts.tooltip; + RGB shadowC = new RGB(0, 0.2); + + int width = font.getWidth(tooltipText); + int h = font.getHeight(); + + if (Mouse.getX() + 20 + width + 100 > App.inst.getSize().x) { + Coord pos = mouse.add(-20, -10 - (h / 2), 60); + font.drawFuzzy(pos, tooltipText, Align.RIGHT, tooltipColor, shadowC, 2); + } else { + Coord pos = mouse.add(20, -10 - (h / 2), 60); + font.drawFuzzy(pos, tooltipText, Align.LEFT, tooltipColor, shadowC, 2); + } + } + + glPopMatrix(); + + } + + + private Coord getRelativeMousePos() + { + Coord lower = getLowerPoint(); + return new Coord(Mouse.getX() - lower.x, Mouse.getY() - lower.y); + } + + + /** + * Get lower point of this root widget - base for relative coordinates. + * + * @return lower coord. + */ + private Coord getLowerPoint() + { + Coord cSize = child.getSize(); + Coord wSize = App.inst.getSize(); + Coord lower = new Coord(); + + switch (alignH) { + case LEFT: + lower.x = child.margins.left; + break; + case CENTER: + lower.x = (wSize.x - cSize.x) / 2; + break; + case RIGHT: + lower.x = wSize.x - cSize.x - child.margins.right; + break; + } + + switch (alignV) { + case BOTTOM: + lower.y = child.margins.bottom; + break; + case CENTER: + lower.y = (wSize.y - cSize.y) / 2; + break; + case TOP: + lower.y = wSize.y - cSize.y - child.margins.top; + break; + } + + return lower; + } + + + /** + * Remove old focus and focus given widget. + * + * @param newFocus widget to focus or null. + */ + public void setFocus(Widget newFocus) + { + if (focus != null) { + focus.focused = false; + focus.onBlur(); + } + + focus = newFocus; + + if (focus != null) { + focus.focused = true; + focus.onFocus(); + } + + } + + + /** + * Handle mouse button + * + * @param button button id + * @param down is down + */ + public void onMouseButton(int button, boolean down) + { + Widget consumer = child.onMouseButton(getRelativeMousePos(), button, down); + if (consumer != focus && down) { + setFocus(consumer); + } + if (consumer != null) listener.actionPerformed(consumer); + } + + + /** + * handle mouse scroll + * + * @param scroll -1,0,1 + */ + public void onScroll(int scroll) + { + Widget consumer = child.onScroll(getRelativeMousePos(), scroll); + if (consumer != focus && consumer != null) { + setFocus(consumer); + } + if (consumer != null) listener.actionPerformed(consumer); + } + + + /** + * handle key press + * + * @param key key index + * @param chr character typed + * @param down + */ + public void onKeyDown(int key, char chr, boolean down) + { + Widget consumer = child.onKey(key, chr, down); + if (consumer != focus && consumer != null) { + setFocus(consumer); + } + if (consumer != null) listener.actionPerformed(consumer); + } + + + /** + * Handle analog inputs + */ + public void handleStaticInputs() + { + child.handleStaticInputs(getRelativeMousePos()); + } + + + /** + * Recalculate children positions and sizes. + */ + public void updatePositions() + { + for (IRefreshable w : needingRefresh) { + w.refresh(); + } + + child.setGuiRoot(this); + child.calcChildSizes(); + + } + + + /** + * Get the panel. + * + * @return panel + */ + public Panel getPanel() + { + return panel; + } + + + @Override + public int compareTo(GuiRoot o) + { + return Double.valueOf(renderIndex).compareTo(Double.valueOf(o.renderIndex)); + } + +} diff --git a/src/net/tortuga/gui/widgets/IRefreshable.java b/src/net/tortuga/gui/widgets/IRefreshable.java new file mode 100644 index 0000000..0c34e4a --- /dev/null +++ b/src/net/tortuga/gui/widgets/IRefreshable.java @@ -0,0 +1,7 @@ +package net.tortuga.gui.widgets; + + +public interface IRefreshable { + + public void refresh(); +} diff --git a/src/net/tortuga/gui/widgets/IScrollable.java b/src/net/tortuga/gui/widgets/IScrollable.java new file mode 100644 index 0000000..d4bcad7 --- /dev/null +++ b/src/net/tortuga/gui/widgets/IScrollable.java @@ -0,0 +1,41 @@ +package net.tortuga.gui.widgets; + + +/** + * Scrollable element + * + * @author MightyPork + */ +public interface IScrollable { + + /** + * Get height of the entire content + * + * @return content height + */ + public double getContentSize(); + + + /** + * When scrollbar is connected + * + * @param scrollbar scrollbar + */ + public void onScrollbarConnected(IScrollbar scrollbar); + + + /** + * Get view height (height of the visible area) + * + * @return view height + */ + public double getViewSize(); + + + /** + * Hook called when scrollbar value changes + * + * @param value scrollbar value 0-1 + */ + public void onScrollbarChange(double value); +} diff --git a/src/net/tortuga/gui/widgets/IScrollbar.java b/src/net/tortuga/gui/widgets/IScrollbar.java new file mode 100644 index 0000000..294307a --- /dev/null +++ b/src/net/tortuga/gui/widgets/IScrollbar.java @@ -0,0 +1,35 @@ +package net.tortuga.gui.widgets; + + +/** + * Scrollable element + * + * @author MightyPork + */ +public interface IScrollbar { + + /** + * Set scrollbar value + * + * @param value value 0-1 + */ + public void setValue(double value); + + + /** + * Get scrollbar value + * + * @return value 0-1 + */ + public double getValue(); + + + /** + * Perform scrolling from wheel on another widget + * + * @param scrollscroll wheel steps + * @return this (widget) + */ + public Widget onScrollDelegate(int scroll); + +} diff --git a/src/net/tortuga/gui/widgets/IWidget.java b/src/net/tortuga/gui/widgets/IWidget.java new file mode 100644 index 0000000..9c9231b --- /dev/null +++ b/src/net/tortuga/gui/widgets/IWidget.java @@ -0,0 +1,310 @@ +package net.tortuga.gui.widgets; + + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * GUI widget interface + * + * @author MightyPork + */ +public interface IWidget { + + /** + * set widget required margins in layout. + * + * @param left + * @param top + * @param right + * @param bottom + * @return this + */ + public Widget setMargins(int left, int top, int right, int bottom); + + + /** + * set widget required margins in layout. + * + * @param left + * @param right + * @return this + */ + public Widget setMarginsH(int left, int right); + + + /** + * set widget required margins in layout. + * + * @param top + * @param bottom + * @return this + */ + public Widget setMarginsV(int top, int bottom); + + + /** + * Set text + * + * @param text text to set + * @return this + */ + public Widget setText(String text); + + + /** + * Get display text + * + * @return text + */ + public String getText(); + + + /** + * Set widget id + * + * @param id + * @return this + */ + public Widget setId(int id); + + + /** + * get id + * + * @return id + */ + public int getId(); + + + /** + * Enable widget (eg. button) + * + * @param state + * @return this + */ + public Widget setEnabled(boolean state); + + + /** + * @return is enabled + */ + public boolean isEnabled(); + + + /** + * Set widget visible + * + * @param state + * @return this + */ + public Widget setVisible(boolean state); + + + /** + * @return is visible + */ + public boolean isVisible(); + + + /** + * Set minimal size in layout + * + * @param size size to set + * @return this + */ + public IWidget setMinSize(Coord size); + + + /** + * Set minimal size in layout + * + * @param w width + * @param h height + * @return this + */ + public IWidget setMinSize(int w, int h); + + + /** + * Get minimal size (set by setMinSize) + * + * @return size width,height + */ + public Coord getMinSize(); + + + /** + * Get current size (in fact a size of getRect()) + * + * @return the size width/height + */ + public Coord getSize(); + + + /** + * Set bounding rectangle.
+ * The margin is excluded from this area. + * + * @param rect + * @return this + */ + public Widget setRect(Rect rect); + + + /** + * Get bounding rectangle + * + * @return bounding rectangle + */ + public Rect getRect(); + + + /** + * Get minimal widget margin + * + * @return margin (left,right,up,down) + */ + public LeftTopRightBottom getMargins(); + + + /** + * Check if mouse is over. + * + * @param mouse mouse coord + * @return is over + */ + public boolean isMouseOver(Coord mouse); + + + /** + * Do render as 2D + * + * @param mouse current mouse position, for hover effects + */ + public void render(Coord mouse); + + + /** + * On focus gained + */ + public void onFocus(); + + + /** + * On focus lost + */ + public void onBlur(); + + + /** + * Check if this widget has focus + * + * @return has focus + */ + public boolean hasFocus(); + + + /** + * Handle click (do effects etc.) + * + * @param pos mouse position + * @param button mouse button + * @param down true if the button was pressed + * @return this if event consumed + */ + public Widget onMouseButton(Coord pos, int button, boolean down); + + + /** + * Handle scroll event + * + * @param pos mouse position + * @param scroll scroll (-1,0,1) + * @return this if event consumed + */ + public Widget onScroll(Coord pos, int scroll); + + + /** + * Key pressed + * + * @param key key index + * @param chr character typed + * @return this if event consumed + */ + public Widget onKey(int key, char chr, boolean down); + + + /** + * Check if can add child + * + * @return can add child to this widget? + */ + public boolean canAddChild(); + + + /** + * Add child + * + * @param child + */ + public void add(Widget child); + + + /** + * Remove child widget + * + * @param child removed child + */ + public void removeChild(Widget child); + + + /** + * remove all childs + */ + public void removeAll(); + + + /** + * calculate size based on contents and childs + */ + public void calcChildSizes(); + + + /** + * set min height + * + * @param h + * @return this + */ + public IWidget setMinHeight(int h); + + + /** + * set min width + * + * @param w + * @return this + */ + public IWidget setMinWidth(int w); + + + /** + * Get gui container + * + * @return root + */ + public GuiRoot getGuiRoot(); + + + /** + * Set gui container + * + * @param guiContainer + * @return + */ + public IWidget setGuiRoot(GuiRoot guiContainer); + +} diff --git a/src/net/tortuga/gui/widgets/IWidgetFactory.java b/src/net/tortuga/gui/widgets/IWidgetFactory.java new file mode 100644 index 0000000..cafe552 --- /dev/null +++ b/src/net/tortuga/gui/widgets/IWidgetFactory.java @@ -0,0 +1,7 @@ +package net.tortuga.gui.widgets; + + +public interface IWidgetFactory { + + public Widget getWidget(); +} diff --git a/src/net/tortuga/gui/widgets/LeftTopRightBottom.java b/src/net/tortuga/gui/widgets/LeftTopRightBottom.java new file mode 100644 index 0000000..56f902b --- /dev/null +++ b/src/net/tortuga/gui/widgets/LeftTopRightBottom.java @@ -0,0 +1,118 @@ +package net.tortuga.gui.widgets; + + +/** + * Set of 4 numbers for sides (left, right, top, bottom) + * + * @author MightyPork + */ +public class LeftTopRightBottom { + + /** left value */ + public int left; + /** top value */ + public int top; + /** right value */ + public int right; + /** bottom value */ + public int bottom; + + + /** + * new set of numbers + * + * @param left + * @param top + * @param right + * @param bottom + */ + public LeftTopRightBottom(int left, int top, int right, int bottom) { + setTo(left, top, right, bottom); + } + + + /** + * new set of numbers + * + * @param other + */ + public LeftTopRightBottom(LeftTopRightBottom other) { + setTo(other); + } + + + /** + * set numbers to.... + * + * @param left + * @param top + * @param right + * @param bottom + */ + public void setTo(int left, int top, int right, int bottom) + { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + + + /** + * set numbers to.... + * + * @param other other sidenums obj + */ + public void setTo(LeftTopRightBottom other) + { + this.left = other.left; + this.top = other.top; + this.right = other.right; + this.bottom = other.bottom; + } + + + /** + * get copy multiplied by some number. + * + * @param mul multiplier + * @return copy multiplied + */ + public LeftTopRightBottom mul(int mul) + { + return new LeftTopRightBottom(left * mul, top * mul, right * mul, bottom * mul); + } + + + /** + * Get a copy + * + * @return copy + */ + public LeftTopRightBottom copy() + { + return new LeftTopRightBottom(this); + } + + + /** + * Get sum of horizontal numbers + * + * @return left + right + */ + public int getHorizontal() + { + return left + right; + } + + + /** + * Get sum of vertical numbers + * + * @return top + bottom + */ + public int getVetical() + { + return top + bottom; + } +} diff --git a/src/net/tortuga/gui/widgets/Theme.java b/src/net/tortuga/gui/widgets/Theme.java new file mode 100644 index 0000000..b22987a --- /dev/null +++ b/src/net/tortuga/gui/widgets/Theme.java @@ -0,0 +1,37 @@ +package net.tortuga.gui.widgets; + + +import net.tortuga.fonts.Fonts; +import net.tortuga.gui.widgets.display.Text; +import net.tortuga.gui.widgets.layout.frame.FrameWindow; + +import com.porcupine.color.RGB; + + +public class Theme { + + public static final RGB TITLE = new RGB(0xffffff, 1); + public static final RGB TITLE_BLUR = new RGB(0x000000, 0.1); + public static final RGB TEXT = new RGB(0x101010, 1); + + + public static Text mkTitle(String text) + { + Text tx = new Text(text); + tx.setColorText(TITLE); + tx.setBlur(TITLE_BLUR, 2, true); + tx.setFont(Fonts.gui_title); + tx.setMargins(0, 5, 0, 1); + + return tx; + } + + + public static FrameWindow mkWindow() + { + FrameWindow frame = new FrameWindow(); + frame.setPadding(20, 20, 25, 25); + frame.enableShadow(true); + return frame; + } +} diff --git a/src/net/tortuga/gui/widgets/Widget.java b/src/net/tortuga/gui/widgets/Widget.java new file mode 100644 index 0000000..c5ab22d --- /dev/null +++ b/src/net/tortuga/gui/widgets/Widget.java @@ -0,0 +1,562 @@ +package net.tortuga.gui.widgets; + + +import java.util.Random; + +import net.tortuga.annotations.Unimplemented; +import net.tortuga.fonts.Fonts; +import net.tortuga.fonts.LoadedFont; +import net.tortuga.gui.panels.Panel; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Abstract widget class + * + * @author MightyPork + */ +public abstract class Widget implements IWidget, Comparable { + + /** a RNG */ + public static Random rand = new Random(); + + /** + * Render index used in layouts to allow safe overlapping of transparent + * parts + */ + public double renderIndex = 0; + + /** Theme */ + public ETheme theme = ETheme.WIDGET; + + /** Widget text */ + public String text = ""; + /** Text render font */ + public LoadedFont font = Fonts.gui; + /** widget id */ + public int id = -1; + /** flag is visible */ + public boolean visible = true; + /** flag is enabled */ + public boolean enabled = true; + /** has focus */ + public boolean focused = false; + /** bounding rectangle */ + public Rect rect = new Rect(); + /** required margins */ + public LeftTopRightBottom margins = new LeftTopRightBottom(5, 5, 5, 5); + /** minimal size */ + public Coord minSize = new Coord(); + /** Gui hierarchy root */ + public GuiRoot guiRoot; + /** Widget tag */ + public String tag = ""; + /** Selected flag (for toggle buttons and similar) */ + public boolean selected = false; + /** Clicked flag */ + public boolean clicked = false; + + + /** + * Get widget font + * + * @return font name + */ + public final LoadedFont getFont() + { + return font; + } + + + /** + * Set widget font + * + * @param font font name + * @return this + */ + public final Widget setFont(LoadedFont font) + { + this.font = font; + return this; + } + + + /** + * Get if widget is focused + * + * @return is focused + */ + public final boolean isFocused() + { + return focused; + } + + + /** + * Set widget focused + * + * @param focused focused flag + * @return this + */ + public final Widget setFocused(boolean focused) + { + this.focused = focused; + return this; + } + + + /** + * Get theme + * + * @return theme + */ + public final ETheme getTheme() + { + return theme; + } + + + /** + * Set widget theme + * + * @param theme theme to set + * @return this + */ + public final Widget setTheme(ETheme theme) + { + this.theme = theme; + return this; + } + + + /** + * Get if this widget has "SELECTED" flag. + * + * @return has selected flag. + */ + public final boolean isSelected() + { + return selected; + } + + + /** + * Get if this widget has "CLICKED" flag (used in buttons and similar). + * + * @return has selected flag. + */ + public final boolean isClicked() + { + return clicked; + } + + + /** + * Set "SELECTED" flag. + * + * @param state flag state + * @return this + */ + public final Widget setSelected(boolean state) + { + selected = state; + return this; + } + + + /** + * Set "CLICKED" flag. + * + * @param state flag state + * @return this + */ + public final Widget setClicked(boolean state) + { + clicked = state; + return this; + } + + + /** + * Get color from theme + * + * @param role color role + * @param mouse mouse position + * @return RGB color + */ + public final RGB getColor(EColorRole role, Coord mouse) + { + ColorScheme cs = ColorScheme.getForTheme(theme); + return cs.getColor(role, isPanelOnTop(), enabled, isMouseOver(mouse), isClicked(), isSelected()); + } + + + /** + * Get color from theme + * + * @param role color role + * @param mouse mouse position + * @param hover (explicitly set HOVER flag) + * @return RGB color + */ + public final RGB getColor(EColorRole role, Coord mouse, boolean hover) + { + ColorScheme cs = ColorScheme.getForTheme(theme); + return cs.getColor(role, isPanelOnTop(), enabled, hover, isClicked(), isSelected()); + } + + + /** + * Get color from theme + * + * @param role color role + * @return RGB color + */ + public final RGB getColor(EColorRole role) + { + ColorScheme cs = ColorScheme.getForTheme(theme); + return cs.getColor(role, isPanelOnTop(), enabled, false, false, isSelected()); + } + + + /** + * Set widget tag + * + * @param tag tag + * @return this + */ + public final Widget setTag(String tag) + { + this.tag = tag; + return this; + } + + + /** + * Get widget tag + * + * @return tag + */ + public final String getTag() + { + return tag; + } + + + @Override + public final Widget setMargins(int left, int top, int right, int bottom) + { + margins.setTo(left, top, right, bottom); + return this; + } + + + @Override + public final Widget setMarginsH(int left, int right) + { + margins.left = left; + margins.right = right; + return this; + } + + + @Override + public final Widget setMarginsV(int top, int bottom) + { + margins.top = top; + margins.bottom = bottom; + return this; + } + + + @Override + public final LeftTopRightBottom getMargins() + { + return margins; //.mul(App.fs12()); + } + + + @Override + public Widget setText(String text) + { + this.text = text; + return this; + } + + + @Override + public final String getText() + { + return text; + } + + + @Override + public final Widget setId(int id) + { + this.id = id; + return this; + } + + + @Override + public final int getId() + { + return id; + } + + + @Override + public final Widget setEnabled(boolean state) + { + enabled = state; + return this; + } + + + @Override + public final boolean isEnabled() + { + return enabled; + } + + + @Override + public final Widget setVisible(boolean state) + { + visible = state; + return this; + } + + + @Override + public final boolean isVisible() + { + return visible; + } + + + @Override + public final Widget setMinSize(Coord size) + { + minSize.setTo(size); + return this; + } + + + @Override + public final Widget setMinSize(int w, int h) + { + minSize.setTo(w, h); + return this; + } + + + @Override + public final Widget setMinWidth(int w) + { + minSize.x = w; + return this; + } + + + @Override + public final Widget setMinHeight(int h) + { + minSize.y = h; + return this; + } + + + @Override + public final Coord getMinSize() + { + return minSize; + } + + + @Override + public final Coord getSize() + { + return rect.getSize(); + } + + + @Override + public final Widget setRect(Rect rect) + { + rect.setTo(rect); + return this; + } + + + @Override + public final Rect getRect() + { + return rect; + } + + + @Override + public boolean isMouseOver(Coord mouse) + { + return rect.isInside(mouse); + } + + + @Override + public abstract void render(Coord mouse); + + + @Override + @Unimplemented + public void onFocus() + {} + + + @Override + @Unimplemented + public void onBlur() + {} + + + @Override + public final boolean hasFocus() + { + return focused; + } + + + @Override + public abstract Widget onMouseButton(Coord pos, int button, boolean down); + + + @Override + public abstract Widget onScroll(Coord pos, int scroll); + + + @Override + public abstract Widget onKey(int key, char chr, boolean down); + + + @Override + public boolean canAddChild() + { + return false; + } + + + @Override + @Unimplemented + public void add(Widget child) + {} + + + @Override + @Unimplemented + public void removeChild(Widget child) + {} + + + @Override + @Unimplemented + public void removeAll() + {} + + + @Override + public abstract void calcChildSizes(); + + + @Override + public final GuiRoot getGuiRoot() + { + return guiRoot; + } + + + /** + * Get root panel + * + * @return panel + */ + public final Panel getPanel() + { + return guiRoot.getPanel(); + } + + + /** + * Get if the panel is on top. + * + * @return is opn top. + */ + public final boolean isPanelOnTop() + { + return getGuiRoot().isPanelOnTop(); + } + + + @Override + public IWidget setGuiRoot(GuiRoot guiContainer) + { + this.guiRoot = guiContainer; + return this; + } + + + /** + * Render tooltip for current widget + * + * @param text text + * @param color color + */ + public void renderTooltip(String text, RGB color) + { + getGuiRoot().setTooltip(text, color); + } + + + /** + * Render white tooltip for current widget + * + * @param text text + */ + public void renderTooltip(String text) + { + getGuiRoot().setTooltip(text, RGB.WHITE); + } + + + /** + * Handle static inputs (mouse coord given, in case it be needed) + * + * @param pos mouse + */ + @Unimplemented + public void handleStaticInputs(Coord pos) + {} + + + @Override + public int compareTo(Widget o) + { + return Double.valueOf(renderIndex).compareTo(Double.valueOf(o.renderIndex)); + } + + + /** + * Set rendering index + * + * @param index index + * @return this + */ + public Widget setRenderIndex(double index) + { + this.renderIndex = index; + return this; + } +} diff --git a/src/net/tortuga/gui/widgets/composite/BeltGrainSlot.java b/src/net/tortuga/gui/widgets/composite/BeltGrainSlot.java new file mode 100644 index 0000000..9946215 --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/BeltGrainSlot.java @@ -0,0 +1,106 @@ +package net.tortuga.gui.widgets.composite; + + +public class BeltGrainSlot implements IDragSlot { + + public DragStone stone = null; + private PgmBeltCell cell; + private int index; + + + /** + * Belt cell grain slot + * + * @param cell the cell + * @param index index: 0 top, 1 bottom + */ + public BeltGrainSlot(PgmBeltCell cell, int index) { + this.cell = cell; + this.index = index; + } + + + @Override + public IDragStone getStoneInSlot() + { + return stone; + } + + + @Override + public boolean isMouseOver() + { + return cell.isMouseOverGrain(index); + } + + + @Override + public boolean hasStone() + { + return stone != null; + } + + + @Override + public IDragStone grabStone() + { + DragStone st = stone; + stone = null; + return st; + } + + + @Override + public boolean acceptsStone(IDragStone stone) + { + if (!(stone instanceof DragStone)) return false; + if (!((DragStone) stone).isGrain()) return false; + + return cell.doesGrainSlotAcceptStone((DragStone) stone, index); + } + + + @Override + public boolean putStone(IDragStone stone) + { + if (acceptsStone(stone)) { + this.stone = (DragStone) stone; + return true; + } + + return false; + } + + + @Override + public void returnStone(IDragStone stone) + { + if (hasStone()) return; + this.stone = (DragStone) stone; + } + + + @Override + public void onStoneCreated(DragStone stone) + {} + + + @Override + public void onStoneDestroyed(DragStone stone) + {} + + + @Override + public boolean isShopSlot() + { + return false; + } + + + @Override + public void deleteStone() + { + stone = null; + } + +} diff --git a/src/net/tortuga/gui/widgets/composite/BeltStoneSlot.java b/src/net/tortuga/gui/widgets/composite/BeltStoneSlot.java new file mode 100644 index 0000000..ae12328 --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/BeltStoneSlot.java @@ -0,0 +1,120 @@ +package net.tortuga.gui.widgets.composite; + + +public class BeltStoneSlot implements IDragSlot { + + public DragStone stone = null; + private PgmBeltCell cell; + + + /** + * Belt cell grain slot + * + * @param cell the cell + * @param index index: 0 top, 1 bottom + */ + public BeltStoneSlot(PgmBeltCell cell) { + this.cell = cell; + } + + + @Override + public IDragStone getStoneInSlot() + { + return stone; + } + + + @Override + public boolean isMouseOver() + { + return cell.isMouseOverStone(); + } + + + @Override + public boolean hasStone() + { + return stone != null; + } + + + @Override + public IDragStone grabStone() + { + DragStone topGrain = cell.topGrain.stone; + DragStone bottomGrain = cell.bottomGrain.stone; + + DragStoneExtended st = new DragStoneExtended(topGrain, stone, bottomGrain); + + stone = null; + cell.topGrain.stone = null; + cell.bottomGrain.stone = null; + return st; + } + + + @Override + public boolean acceptsStone(IDragStone stone) + { + if (stone instanceof DragStone) return ((DragStone) stone).isStone(); + + return true; + } + + + @Override + public boolean putStone(IDragStone stone) + { + if (acceptsStone(stone)) { + returnStone(stone); + + return true; + } + + return false; + } + + + @Override + public void returnStone(IDragStone stone) + { + if (hasStone()) return; + if (stone instanceof DragStone) { + this.stone = (DragStone) stone; + } else { + DragStoneExtended ext = (DragStoneExtended) stone; + + cell.topGrain.stone = ext.getTopStone(); + cell.bottomGrain.stone = ext.getBottomStone(); + this.stone = ext.getMiddleStone(); + } + } + + + @Override + public void deleteStone() + { + stone = null; + cell.topGrain.stone = null; + cell.bottomGrain.stone = null; + } + + + @Override + public void onStoneCreated(DragStone stone) + {} + + + @Override + public void onStoneDestroyed(DragStone stone) + {} + + + @Override + public boolean isShopSlot() + { + return false; + } + +} diff --git a/src/net/tortuga/gui/widgets/composite/CompositeScrollBoxH.java b/src/net/tortuga/gui/widgets/composite/CompositeScrollBoxH.java new file mode 100644 index 0000000..ade2193 --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/CompositeScrollBoxH.java @@ -0,0 +1,109 @@ +package net.tortuga.gui.widgets.composite; + + +import java.util.ArrayList; +import java.util.List; + +import net.tortuga.annotations.Internal; +import net.tortuga.gui.widgets.IRefreshable; +import net.tortuga.gui.widgets.IWidgetFactory; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.gui.widgets.input.ScrollbarH; +import net.tortuga.gui.widgets.layout.LayoutV; +import net.tortuga.gui.widgets.layout.ScrollingLayoutH; +import net.tortuga.util.Align; + + +public class CompositeScrollBoxH extends LayoutV implements IRefreshable { + + private ScrollingLayoutH layoutH; + private ScrollbarH scrollbar; + + + public CompositeScrollBoxH(int size, IWidgetFactory fakeWidgetFactory, List elements, int alignH, int alignV) { + super(Align.CENTER, Align.TOP); + + layoutH = new ScrollingLayoutH(size, fakeWidgetFactory); + layoutH.setAlign(alignH, alignV); + + for (Widget w : elements) { + layoutH.addChild(w); + } + + add(layoutH); + + add(scrollbar = new ScrollbarH((int) layoutH.getSize().x, layoutH)); + scrollbar.setMargins(3, 3, 3, 3); + } + + + public void adaptForSize(int size) + { + layoutH.adaptForSize(size); + scrollbar.setMinWidth((int) layoutH.getSize().x); + } + + + public CompositeScrollBoxH enableDelegatedScroll(boolean state) + { + layoutH.delegatedScrollEnabled = state; + return this; + } + + + public CompositeScrollBoxH(int size, IWidgetFactory fakeWidgetFactory) { + this(size, fakeWidgetFactory, new ArrayList(), Align.CENTER, Align.CENTER); + } + + + public CompositeScrollBoxH addItem(Widget item) + { + item.setGuiRoot(getGuiRoot()); + layoutH.addChild(item); + return this; + } + + + public ArrayList getItems() + { + return layoutH.children; + } + + + /** + * Refresh positions and contents after some children were added or removed. + */ + @Override + public void refresh() + { + setGuiRoot(getGuiRoot()); + layoutH.onScrollbarChange(scrollbar.getValue()); + setGuiRoot(getGuiRoot()); + //scb.setValue(0); + } + + + @Override + @Internal + public void add(Widget child) + { + super.add(child); + child.setGuiRoot(getGuiRoot()); + } + + + @Override + public void removeAll() + { + layoutH.removeAll(); + } + + + @Override + public void calcChildSizes() + { + super.calcChildSizes(); + + getGuiRoot().scheduleRefresh(this); + } +} diff --git a/src/net/tortuga/gui/widgets/composite/CompositeScrollBoxV.java b/src/net/tortuga/gui/widgets/composite/CompositeScrollBoxV.java new file mode 100644 index 0000000..fe22b0e --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/CompositeScrollBoxV.java @@ -0,0 +1,109 @@ +package net.tortuga.gui.widgets.composite; + + +import java.util.ArrayList; +import java.util.List; + +import net.tortuga.annotations.Internal; +import net.tortuga.gui.widgets.IRefreshable; +import net.tortuga.gui.widgets.IWidgetFactory; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.gui.widgets.input.ScrollbarV; +import net.tortuga.gui.widgets.layout.LayoutH; +import net.tortuga.gui.widgets.layout.ScrollingLayoutV; +import net.tortuga.util.Align; + + +public class CompositeScrollBoxV extends LayoutH implements IRefreshable { + + private ScrollingLayoutV layoutV; + private ScrollbarV scrollbar; + + + public CompositeScrollBoxV(int size, IWidgetFactory fakeWidgetFactory, List elements, int alignH, int alignV) { + super(Align.CENTER, Align.TOP); + + layoutV = new ScrollingLayoutV(size, fakeWidgetFactory); + layoutV.setAlign(alignH, alignV); + + for (Widget w : elements) { + layoutV.addChild(w); + } + + add(layoutV); + + add(scrollbar = new ScrollbarV((int) layoutV.getSize().y, layoutV)); + scrollbar.setMargins(3, 3, 3, 3); + } + + + public CompositeScrollBoxV(int size, IWidgetFactory fakeWidgetFactory) { + this(size, fakeWidgetFactory, new ArrayList(), Align.CENTER, Align.TOP); + } + + + public CompositeScrollBoxV addItem(Widget item) + { + item.setGuiRoot(getGuiRoot()); + layoutV.addChild(item); + return this; + } + + + public CompositeScrollBoxV enableDelegatedScroll(boolean state) + { + layoutV.delegatedScrollEnabled = state; + return this; + } + + + public ArrayList getItems() + { + return layoutV.children; + } + + + public void adaptForSize(int size) + { + layoutV.adaptForSize(size); + scrollbar.setMinHeight((int) layoutV.getSize().y); + } + + + /** + * Refresh positions and contents after some children were added or removed. + */ + @Override + public void refresh() + { + setGuiRoot(getGuiRoot()); + layoutV.onScrollbarChange(scrollbar.getValue()); + setGuiRoot(getGuiRoot()); + //scb.setValue(0); + } + + + @Override + @Internal + public void add(Widget child) + { + super.add(child); + child.setGuiRoot(getGuiRoot()); + } + + + @Override + public void removeAll() + { + layoutV.removeAll(); + } + + + @Override + public void calcChildSizes() + { + super.calcChildSizes(); + + getGuiRoot().scheduleRefresh(this); + } +} diff --git a/src/net/tortuga/gui/widgets/composite/DragDropServer.java b/src/net/tortuga/gui/widgets/composite/DragDropServer.java new file mode 100644 index 0000000..efc8932 --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/DragDropServer.java @@ -0,0 +1,408 @@ +package net.tortuga.gui.widgets.composite; + + +import java.util.HashSet; +import java.util.Set; + +import net.tortuga.gui.widgets.Widget; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.sounds.Effects; + +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import com.porcupine.coord.Coord; + + +/** + * Drag'n'Drop server widget (should be placed below all widgets that use it) - + * handles dragging and rendering. + * + * @author MightyPork + */ +public class DragDropServer extends Widget { + + private Set slots = new HashSet(); + + /** Dragged stone */ + public IDragStone dragged = null; + private IDragSlot dragOriginSlot = null; + /** + * Paint mode (click does not discard the dragged piece, allowing multiple + * to be built) + */ + public boolean paintMode = false; + public boolean paintOneOnly = false; + + private IDragSlot lastEventSlot = null; + + + public void registerSlot(IDragSlot slot) + { + slots.add(slot); + } + + + /** + * New DND server + */ + public DragDropServer() { + setMargins(0, 0, 0, 0); + setRenderIndex(1000); + } + + + @Override + public void render(Coord mouse) + { + // draw dragged stone + if (isDragging()) { + dragged.render(mouse); + } + + } + + + /** + * Get if can start dragging (free hands) + * + * @return can start dragging + */ + public boolean canStartDrag() + { + return !isDragging(); + } + + + /** + * Select stone for dragging + * + * @param iDragStone + */ + public void startDrag(IDragStone iDragStone) + { + dragged = iDragStone; + paintMode = false; + paintOneOnly = false; + } + + + /** + * Enter paint mode (mouse went up after going down to start dragging - only + * in shops!) + */ + public void enterPaintMode() + { + paintMode = true; + } + + + /** + * Get if is dragging + * + * @return is dragging + */ + public boolean isDragging() + { + return dragged != null; + } + + + /** + * Get dragged stone + * + * @return the dragged stone, if any + */ + public IDragStone getDragged() + { + return dragged; + } + + + public void notifySlotsStoneCreated(IDragStone stone) + { + if (stone == null) return; + if (stone instanceof DragStone) { + for (IDragSlot slot : slots) { + if (slot.isShopSlot()) slot.onStoneCreated((DragStone) stone); + } + } else { + DragStoneExtended dse = (DragStoneExtended) stone; + notifySlotsStoneCreated(dse.getTopStone()); + notifySlotsStoneCreated(dse.getMiddleStone()); + notifySlotsStoneCreated(dse.getBottomStone()); + } + } + + + public void notifySlotsStoneDestroyed(IDragStone stone) + { + if (stone == null) { + return; + } + if (stone instanceof DragStone) { + for (IDragSlot slot : slots) { + if (slot.isShopSlot()) slot.onStoneDestroyed((DragStone) stone); + } + } else { + DragStoneExtended dse = (DragStoneExtended) stone; + notifySlotsStoneDestroyed(dse.getTopStone()); + notifySlotsStoneDestroyed(dse.getMiddleStone()); + notifySlotsStoneDestroyed(dse.getBottomStone()); + } + } + + + public static void playDrag() + { + Effects.play("gui.program.grab"); + } + + + public static void playDrop() + { + Effects.play("gui.program.drop"); + } + + + public static void playDelete() + { + Effects.play("gui.program.delete"); + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + boolean up = !down; + boolean left = button == 0; + boolean right = button == 1; + boolean dragging = isDragging(); + + IDragSlot hoverSlot = null; + for (IDragSlot slot : slots) { + if (slot.isMouseOver()) { + hoverSlot = slot; + break; + } + } + + if (!dragging && hoverSlot != null && Keyboard.isKeyDown(Keyboard.KEY_DELETE) && left && down) { + if (hoverSlot.isShopSlot()) return this; + if (!hoverSlot.hasStone()) return this; + playDelete(); + notifySlotsStoneDestroyed(hoverSlot.getStoneInSlot()); + hoverSlot.deleteStone(); + } + + if (!dragging && hoverSlot != null && left && down) { + // start drag if there's anything in this slot + if (hoverSlot.hasStone()) { + playDrag(); + startDrag(hoverSlot.grabStone()); + lastEventSlot = hoverSlot; + dragOriginSlot = hoverSlot; + } + + return this; + } + + if (dragging && !paintMode && hoverSlot != null && hoverSlot == lastEventSlot && left && up) { + // paint mode, one only + + enterPaintMode(); + lastEventSlot = null; + + if (!hoverSlot.isShopSlot() || ((DragStone) dragged).getStoneBase().isSingleInstance()) { + paintOneOnly = true; + } + return this; + } + + if (dragging && !paintMode && (hoverSlot == null || !hoverSlot.acceptsStone(dragged)) && up && left) { + // mouse up outside slots - drop dragged stone + dragOriginSlot.returnStone(dragged); + dragged = null; + paintMode = false; + lastEventSlot = null; + + playDrop(); + return this; + } + + if (dragging && paintMode && hoverSlot != null && down && left) { + if (hoverSlot.isShopSlot()) { + playDelete(); + notifySlotsStoneDestroyed(dragged); + dragged = null; + paintMode = false; + lastEventSlot = null; + + if (hoverSlot.hasStone()) { + startDrag(hoverSlot.grabStone()); + lastEventSlot = hoverSlot; + dragOriginSlot = hoverSlot; + } + + return this; + } + + // place one of the paint mode + if (hoverSlot.acceptsStone(dragged)) { + if (!hoverSlot.hasStone()) { + playDrop(); + hoverSlot.putStone(dragged.copy()); + if (paintOneOnly) { + dragged = null; + paintMode = false; + lastEventSlot = null; + } + } else { + // has stone. + if (paintOneOnly) { + playDrop(); + IDragStone taken = hoverSlot.grabStone(); + hoverSlot.putStone(dragged); + dragged = taken; + lastEventSlot = null; + dragOriginSlot = hoverSlot; + } + } + } + return this; + } + + if (dragging && paintMode && down && right) { + // right click - discard dragging + playDrop(); + dragOriginSlot.returnStone(dragged); + dragged = null; + paintMode = false; + lastEventSlot = null; + return this; + } + + if (dragging && !paintMode && hoverSlot != null && up && left) { + if (hoverSlot.isShopSlot()) { + playDelete(); + notifySlotsStoneDestroyed(dragged); + dragged = null; + paintMode = false; + lastEventSlot = null; + return this; + } + + // drop in slot + if (hoverSlot.acceptsStone(dragged)) { + if (!hoverSlot.hasStone()) { + playDrop(); + hoverSlot.putStone(dragged); + dragged = null; + paintMode = false; + lastEventSlot = null; + } else { + playDrop(); + IDragStone taken = hoverSlot.grabStone(); + hoverSlot.putStone(dragged); + dragOriginSlot.putStone(taken); + dragged = null; + paintMode = false; + lastEventSlot = null; + } + } + return this; + } + +// if(dragging && !paintMode && hoverSlot != null && down && left) { +// // drop in slot +// if(hoverSlot.acceptsStone(dragged)) { +// hoverSlot.putStone(dragged); +// dragged = null; +// paintMode = false; +// lastEventSlot = null; +// } +// return this; +// } + + return null; + } + + + public boolean isInNumberEditMode() + { + return Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL) + || Mouse.isButtonDown(1); + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + if (!isInNumberEditMode()) { + return null; + } + + IDragSlot hoverSlot = null; + for (IDragSlot slot : slots) { + if (slot.isMouseOver()) { + hoverSlot = slot; + break; + } + } + + if (hoverSlot == null) return null; + if (!hoverSlot.hasStone()) return null; + if (hoverSlot.isShopSlot()) return null; + + IDragStone ids = hoverSlot.getStoneInSlot(); + if (!(ids instanceof DragStone)) return null; + DragStone ds = (DragStone) ids; + + if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)) { + scroll *= 10; + } + + if (ds.isGrain()) { + ProgTileGrain grain = ds.getHexagonStone(); + if (grain.hasExtraNumber()) { + grain.setExtraNumber(grain.getExtraNumber() + scroll); + } + } + + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + if (down && key == Keyboard.KEY_DELETE) { + if (dragged != null) { + playDelete(); + notifySlotsStoneDestroyed(dragged); + } + dragged = null; + paintMode = false; + lastEventSlot = null; + return this; + } + + if (down && key == Keyboard.KEY_ADD) { + onScroll(null, 1); + } + + if (down && key == Keyboard.KEY_SUBTRACT) { + onScroll(null, -1); + } + + return null; + } + + + @Override + public void calcChildSizes() + { + rect.setTo(0, 0, 0, 0); // technical widget + } + +} diff --git a/src/net/tortuga/gui/widgets/composite/DragStone.java b/src/net/tortuga/gui/widgets/composite/DragStone.java new file mode 100644 index 0000000..de15dbd --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/DragStone.java @@ -0,0 +1,87 @@ +package net.tortuga.gui.widgets.composite; + + +import net.tortuga.level.program.tiles.EProgTileType; +import net.tortuga.level.program.tiles.ProgTileBase; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.ProgTileStone; + +import com.porcupine.coord.Coord; + + +public class DragStone implements IDragStone { + + private ProgTileBase stone; + + + public DragStone(ProgTileBase stone) { + this.stone = stone; + } + + + public boolean isStone() + { + return getType() == EProgTileType.STONE; + } + + + public boolean isGrain() + { + return getType() == EProgTileType.GRAIN; + } + + + public EProgTileType getType() + { + return stone.getProgTileType(); + } + + + public ProgTileStone getSquareStone() + { + if (getType() == EProgTileType.STONE) { + return (ProgTileStone) stone; + } else { + return null; + } + } + + + public ProgTileGrain getHexagonStone() + { + if (getType() == EProgTileType.GRAIN) { + return (ProgTileGrain) stone; + } else { + return null; + } + } + + + @Override + public void render(Coord pos) + { + stone.render(pos); + } + + + @Override + public DragStone copy() + { + return new DragStone(stone.copy()); + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null || !(obj instanceof DragStone)) return false; + return ((DragStone) obj).stone.equals(stone); + } + + + public ProgTileBase getStoneBase() + { + return stone; + } + +} diff --git a/src/net/tortuga/gui/widgets/composite/DragStoneExtended.java b/src/net/tortuga/gui/widgets/composite/DragStoneExtended.java new file mode 100644 index 0000000..ff5855e --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/DragStoneExtended.java @@ -0,0 +1,54 @@ +package net.tortuga.gui.widgets.composite; + + +import com.porcupine.coord.Coord; + + +public class DragStoneExtended implements IDragStone { + + private DragStone top; + private DragStone middle; + private DragStone bottom; + + + public DragStoneExtended(DragStone top, DragStone middle, DragStone bottom) { + this.middle = middle; + this.top = top; + this.bottom = bottom; + } + + + public DragStone getMiddleStone() + { + return middle; + } + + + public DragStone getTopStone() + { + return top; + } + + + public DragStone getBottomStone() + { + return bottom; + } + + + @Override + public void render(Coord pos) + { + if (top != null) top.render(pos.add(0, 64)); + middle.render(pos); + if (bottom != null) bottom.render(pos.sub(0, 64)); + } + + + @Override + public IDragStone copy() + { + return new DragStoneExtended(top != null ? top.copy() : null, middle != null ? middle.copy() : null, bottom != null ? bottom.copy() : null); + } + +} diff --git a/src/net/tortuga/gui/widgets/composite/IDragSlot.java b/src/net/tortuga/gui/widgets/composite/IDragSlot.java new file mode 100644 index 0000000..1af441a --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/IDragSlot.java @@ -0,0 +1,91 @@ +package net.tortuga.gui.widgets.composite; + + +/** + * Drag'n'drop stone + * + * @author MightyPork + */ +public interface IDragSlot { + + /** + * Get stone in slot + * + * @return the stone + */ + public IDragStone getStoneInSlot(); + + + /** + * Get if mouse is over this slot + * + * @return is over + */ + public boolean isMouseOver(); + + + /** + * Get if slot has stone available for building + * + * @return has stone + */ + public boolean hasStone(); + + + /** + * Grab stone from slot + * + * @return stone + */ + public IDragStone grabStone(); + + + /** + * Get if this slot can accept given stone (is empty, stone is of right type + * etc.) + * + * @param stone stone to check + * @return can be accepted + */ + public boolean acceptsStone(IDragStone stone); + + + /** + * Put stone to this slot. + * + * @param stone stone + * @return true on success + */ + public boolean putStone(IDragStone stone); + + + /** + * Return dragged stone back to this slot + * + * @param stone the dragged stone + */ + public void returnStone(IDragStone stone); + + + /** + * Called when a stone was placed into program - eg. disable taking of more + * stones etc. + * + * @param stone + */ + public void onStoneCreated(DragStone stone); + + + /** + * Called when a stone was removed from program + * + * @param stone + */ + public void onStoneDestroyed(DragStone stone); + + + public boolean isShopSlot(); + + + public void deleteStone(); +} diff --git a/src/net/tortuga/gui/widgets/composite/IDragStone.java b/src/net/tortuga/gui/widgets/composite/IDragStone.java new file mode 100644 index 0000000..eb63d84 --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/IDragStone.java @@ -0,0 +1,14 @@ +package net.tortuga.gui.widgets.composite; + + +import com.porcupine.coord.Coord; + + +public interface IDragStone { + + public void render(Coord pos); + + + public IDragStone copy(); + +} diff --git a/src/net/tortuga/gui/widgets/composite/PgmBeltCell.java b/src/net/tortuga/gui/widgets/composite/PgmBeltCell.java new file mode 100644 index 0000000..8e61abb --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/PgmBeltCell.java @@ -0,0 +1,251 @@ +package net.tortuga.gui.widgets.composite; + + +import net.tortuga.fonts.Fonts; +import net.tortuga.fonts.LoadedFont; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.level.program.tiles.EGrainSlotMode; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.ProgTileStone; +import net.tortuga.level.program.tiles.grains.GrainJumpRelative; +import net.tortuga.textures.Tx; +import net.tortuga.util.Align; +import net.tortuga.util.RenderUtils; + +import org.lwjgl.input.Keyboard; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +public class PgmBeltCell extends Widget { + + private static final Coord SIZE = new Coord(64, 64 + 62 + 62); + + public int addressCode; + + protected BeltGrainSlot topGrain = new BeltGrainSlot(this, 0); + + protected BeltGrainSlot bottomGrain = new BeltGrainSlot(this, 1); + + protected BeltStoneSlot mainSlot = new BeltStoneSlot(this); + + private DragDropServer server; + + + public PgmBeltCell() { + setMargins(6, 0, 6, 0); + } + + + public PgmBeltCell(int i) { + this.addressCode = i; + } + + private LoadedFont tinyFont = Fonts.tiny; + + + @Override + public void render(Coord mouse) + { + Coord center = rect.getCenter().add_ip(0, 6).round_ip(); + + Rect centralSlot = new Rect(center, center).grow_ip(32, 32); + + Rect topSlot = centralSlot.add(0, 63); + Rect bottomSlot = centralSlot.sub(0, 63); + + boolean top = false; + boolean bottom = false; + + boolean topImp = false; + boolean bottomImp = false; + + if (mainSlot.stone != null) { + EGrainSlotMode topMode = mainSlot.stone.getSquareStone().getGrainModeTop(); + EGrainSlotMode bottomMode = mainSlot.stone.getSquareStone().getGrainModeBottom(); + + top = (topMode != EGrainSlotMode.UNUSED); + bottom = (bottomMode != EGrainSlotMode.UNUSED); + + topImp = (topMode == EGrainSlotMode.REQUIRED); + bottomImp = (bottomMode == EGrainSlotMode.REQUIRED); + } + + // render slots + if (top) RenderUtils.quadTextured(topSlot, Tx.SLOT_HEXAGON); + RenderUtils.quadTextured(centralSlot, Tx.SLOT_SQUARE); + if (bottom) RenderUtils.quadTextured(bottomSlot, Tx.SLOT_HEXAGON); + + LoadedFont font = Fonts.program_number; + Coord midTxtPos = center.sub(2.5, font.getHeight() / 2); + + RGB color = new RGB(0, 0.5); + font.draw(midTxtPos, addressCode + "", color, Align.CENTER); + + if (topImp) RenderUtils.quadTextured(topSlot, Tx.SLOT_WARNING); + if (bottomImp) RenderUtils.quadTextured(bottomSlot, Tx.SLOT_WARNING); + + if (topGrain.stone != null) topGrain.stone.render(center.add(0, 63)); + if (mainSlot.stone != null) mainSlot.stone.render(center); + if (bottomGrain.stone != null) bottomGrain.stone.render(center.sub(0, 63)); + + Integer topNum = null; + Integer bottomNum = null; + + ProgTileGrain grainT = null; + ProgTileGrain grainB = null; + + if (topGrain.hasStone()) { + grainT = ((DragStone) topGrain.getStoneInSlot()).getHexagonStone(); + if (grainT.hasExtraNumber()) topNum = grainT.getExtraNumber(); + } + + if (bottomGrain.hasStone()) { + grainB = ((DragStone) bottomGrain.getStoneInSlot()).getHexagonStone(); + if (grainB.hasExtraNumber()) bottomNum = grainB.getExtraNumber(); + } + + if (topNum != null) { + Coord pos = center.add(-3, 32 + 54).round_ip(); + + String txt = topNum + ""; + + if (grainT instanceof GrainJumpRelative) { + if (topNum > 0) txt = "+" + txt; + } + + if (topGrain.isMouseOver() && server.isInNumberEditMode()) { + color = new RGB(0x33ddff, 1); + tinyFont.drawFuzzy(pos, txt, Align.CENTER, color, new RGB(0, 0.12), 2, true); + } else { + color = new RGB(0, 0.8); + tinyFont.draw(pos, txt, color, Align.CENTER); + } + } + + if (bottomNum != null) { + Coord pos = center.add(-3, -(32 + 57) - tinyFont.getHeight()).round_ip(); + + String txt = bottomNum + ""; + + if (grainB instanceof GrainJumpRelative) { + if (bottomNum > 0) txt = "+" + txt; + } + + if (bottomGrain.isMouseOver() && server.isInNumberEditMode()) { + color = new RGB(0x33ddff, 1); + tinyFont.drawFuzzy(pos, txt, Align.CENTER, color, new RGB(0, 0.12), 2, true); + } else { + color = new RGB(0, 0.8); + tinyFont.draw(pos, txt, color, Align.CENTER); + } + + } + + //RenderUtils.quadBorder(rect, 1, RGB.GREEN, null); + //RenderUtils.quadBorder(rect.getAxisV(), 1, RGB.RED, null); +//mainSlot.stone != null && + if ((Keyboard.isKeyDown(Keyboard.KEY_TAB))) { + color = new RGB(0x3399ff, 1); + RGB blur = new RGB(0, 0.12); + + // on top + font.drawFuzzy(midTxtPos, addressCode + "", Align.CENTER, color, blur, 3, true); + } + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + setMinSize(SIZE.add(0, 25 /*0tinyFont.getHeight()*2*/)); + rect.setTo(minSize); + } + + + public void setServer(DragDropServer dndServer) + { + server = dndServer; + dndServer.registerSlot(mainSlot); + dndServer.registerSlot(topGrain); + dndServer.registerSlot(bottomGrain); + } + + private boolean moTop = false, moMiddle = false, moBottom = false; + + + @Override + public void handleStaticInputs(Coord pos) + { + super.handleStaticInputs(pos); + + moTop = false; + moBottom = false; + moMiddle = false; + + if (isMouseOver(pos)) { + moTop = pos.isInRect(rect.getEdgeTop().growDown(64)); + moBottom = pos.isInRect(rect.getEdgeBottom().growUp(64)); + moMiddle = pos.isInRect(rect.getAxisH().grow(0, 32)); + } + } + + + public boolean isMouseOverGrain(int index) + { + return index == 0 ? moTop : moBottom; + } + + + public boolean isMouseOverStone() + { + return moMiddle; + } + + + public EGrainSlotMode getGrainSlotMode(int index) + { + if (mainSlot.stone == null) return EGrainSlotMode.UNUSED; + + ProgTileStone sq = mainSlot.stone.getSquareStone(); + return index == 0 ? sq.getGrainModeTop() : sq.getGrainModeBottom(); + } + + + public boolean doesGrainSlotAcceptStone(DragStone stone, int index) + { + if (getGrainSlotMode(index) == EGrainSlotMode.UNUSED) return false; + if (!stone.isGrain()) return false; + + ProgTileStone sq = mainSlot.stone.getSquareStone(); + + if (index == 0) return sq.acceptsGrainTop(stone.getHexagonStone().getGrainType()); + if (index == 1) return sq.acceptsGrainBottom(stone.getHexagonStone().getGrainType()); + + return true; + } + +} diff --git a/src/net/tortuga/gui/widgets/composite/PgmShopBase.java b/src/net/tortuga/gui/widgets/composite/PgmShopBase.java new file mode 100644 index 0000000..8f9b985 --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/PgmShopBase.java @@ -0,0 +1,205 @@ +package net.tortuga.gui.widgets.composite; + + +import net.tortuga.annotations.NeedsOverride; +import net.tortuga.annotations.Unimplemented; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.level.program.tiles.ProgTileBase; + +import com.porcupine.coord.Coord; + + +/** + * Program shop slot + * + * @author MightyPork + */ +public abstract class PgmShopBase extends Widget implements IDragSlot { + + private static final Coord SIZE = new Coord(64, 64); + + /** DND server */ + protected DragDropServer server = null; + + /** Stone in slot */ + protected DragStone stone = null; + + protected boolean mouseOver = false; + + protected int createdStones = 0; + + + @Override + public boolean hasStone() + { + return stone != null && (createdStones == 0 || !stone.getStoneBase().isSingleInstance()); + } + + + /** + * Shop slot base + */ + public PgmShopBase() { + setMargins(0, 0, 0, 0); + } + + + @Override + @Unimplemented + public void deleteStone() + {} + + + @Override + public boolean isShopSlot() + { + return true; + } + + + public void setShopStone(ProgTileBase stone) + { + this.stone = new DragStone(stone); + } + + + /** + * Set drag'n'drop server + * + * @param server the server + * @return this + */ + public PgmShopBase setServer(DragDropServer server) + { + this.server = server; + server.registerSlot(this); + return this; + } + + + @Override + public final void render(Coord mouse) + { + Coord center = rect.getCenter(); + renderSlotBackground(center); + + if (hasStone()) { + getStoneInSlot().render(center); + } + } + + + /** + * Render slot background texture + * + * @param center center coord + */ + public abstract void renderSlotBackground(Coord center); + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + setMinSize(SIZE); + rect.setTo(minSize); + } + + + @Override + public DragStone getStoneInSlot() + { + return stone; + } + + + @Override + public boolean acceptsStone(IDragStone stone) + { + return true; // trash for all + } + + + @Override + public boolean putStone(IDragStone stone) + { + // trash function + server.notifySlotsStoneDestroyed(stone); + return true; + } + + + @Override + public void returnStone(IDragStone stone) + { + // discard it + server.notifySlotsStoneDestroyed(stone); + } + + + @Override + @NeedsOverride + public void onStoneCreated(DragStone stone) + { + if (stone.equals(this.stone)) { + createdStones++; + } + } + + + @Override + @NeedsOverride + public void onStoneDestroyed(DragStone stone) + { + if (stone.equals(this.stone)) { + createdStones--; + } + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + return null; + } + + + @Override + public void handleStaticInputs(Coord pos) + { + super.handleStaticInputs(pos); + mouseOver = isMouseOver(pos); + } + + + @Override + public DragStone grabStone() + { + if (hasStone()) { + server.notifySlotsStoneCreated(stone); + return stone.copy(); + } + return null; + } + + + @Override + public boolean isMouseOver() + { + return mouseOver && isVisible() && isEnabled(); + } + +} diff --git a/src/net/tortuga/gui/widgets/composite/PgmShopHexagon.java b/src/net/tortuga/gui/widgets/composite/PgmShopHexagon.java new file mode 100644 index 0000000..7758e91 --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/PgmShopHexagon.java @@ -0,0 +1,30 @@ +package net.tortuga.gui.widgets.composite; + + +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.textures.Tx; +import net.tortuga.util.RenderUtils; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +public class PgmShopHexagon extends PgmShopBase { + + @Override + public void renderSlotBackground(Coord center) + { + Rect centralSlot = rect.getAxisH(); + centralSlot.grow_ip(0, 32); + RenderUtils.quadTextured(centralSlot, Tx.SLOT_HEXAGON); + } + + + public PgmShopHexagon(ProgTileGrain grain) { + setShopStone(grain); + } + + + public PgmShopHexagon() {} + +} diff --git a/src/net/tortuga/gui/widgets/composite/PgmShopSquare.java b/src/net/tortuga/gui/widgets/composite/PgmShopSquare.java new file mode 100644 index 0000000..27a30e2 --- /dev/null +++ b/src/net/tortuga/gui/widgets/composite/PgmShopSquare.java @@ -0,0 +1,29 @@ +package net.tortuga.gui.widgets.composite; + + +import net.tortuga.level.program.tiles.ProgTileStone; +import net.tortuga.textures.Tx; +import net.tortuga.util.RenderUtils; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +public class PgmShopSquare extends PgmShopBase { + + public PgmShopSquare(ProgTileStone stone) { + setShopStone(stone); + } + + + public PgmShopSquare() {} + + + @Override + public void renderSlotBackground(Coord center) + { + Rect centralSlot = rect.getAxisH(); + centralSlot.grow_ip(0, 32); + RenderUtils.quadTextured(centralSlot, Tx.SLOT_SQUARE); + } +} diff --git a/src/net/tortuga/gui/widgets/display/ColorRectange.java b/src/net/tortuga/gui/widgets/display/ColorRectange.java new file mode 100644 index 0000000..32ec06f --- /dev/null +++ b/src/net/tortuga/gui/widgets/display/ColorRectange.java @@ -0,0 +1,116 @@ +package net.tortuga.gui.widgets.display; + + +import net.tortuga.gui.widgets.Widget; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; + + +/** + * Space + * + * @author MightyPork + */ +public class ColorRectange extends Widget { + + /** + * Colour rectange + * + * @param width min width + * @param height min height + * @param margin widget margin + * @param border widget border (bevel in rectangle) + * @param color color (referenced!) + */ + public ColorRectange(int width, int height, int margin, int border, RGB color) { + setMargins(margin, margin, margin, margin); + setMinSize(width, height); + this.border = border; + this.rectColor = color; + } + + public String tooltipText = ""; + public RGB tooltipColor = RGB.WHITE; + + + /** + * Set tooltip + * + * @param text text + * @param color render color + * @return this + */ + public ColorRectange setTooltip(String text, RGB color) + { + tooltipColor = color; + tooltipText = text; + return this; + } + + public int border = 3; + public RGB rectColor = RGB.GREEN; + + + public ColorRectange setColorRect(RGB newColor) + { + rectColor = newColor; + return this; + } + + + public ColorRectange setBorderWidth(int w) + { + border = w; + return this; + } + + private long mouseEnterTime = 0; + + + @Override + public void render(Coord mouse) + { + RenderUtils.quadRectOutset(rect, border, rectColor, false); + + boolean hover = isMouseOver(mouse); + + if (enabled && hover && tooltipText.length() > 0) { + if (System.currentTimeMillis() - mouseEnterTime > 1000) { + renderTooltip(tooltipText, tooltipColor); + } + } else { + mouseEnterTime = System.currentTimeMillis(); + } + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + rect.setTo(0, 0, minSize.x, minSize.y); + } + +} diff --git a/src/net/tortuga/gui/widgets/display/Icon.java b/src/net/tortuga/gui/widgets/display/Icon.java new file mode 100644 index 0000000..84ca883 --- /dev/null +++ b/src/net/tortuga/gui/widgets/display/Icon.java @@ -0,0 +1,177 @@ +package net.tortuga.gui.widgets.display; + + +import static org.lwjgl.opengl.GL11.*; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.textures.TextureManager; +import net.tortuga.util.RenderUtils; + +import org.newdawn.slick.opengl.Texture; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; + + +/** + * Icon 32x32 from texture sheet + * + * @author MightyPork + */ +public class Icon extends Widget { + + private CoordI tile = null; + private Texture texture = null; + + /** Tint color */ + public RGB colorTint = RGB.WHITE.copy(); + + + /** + * Set color tint + * + * @param tint color tint + * @return this + */ + public Icon setColor(RGB tint) + { + colorTint.setTo(tint); + return this; + } + + public String tooltipText = ""; + public RGB tooltipColor = RGB.WHITE; + private long mouseEnterTime = 0; + + + /** + * new icon + * + * @param texture texture + * @param tileX tile x coord + * @param tileY tile y coord + */ + public Icon(Texture texture, int tileX, int tileY) { + this.texture = texture; + this.tile = new CoordI(tileX, tileY); + setMargins(2, 2, 2, 2); + } + + + /** + * Set tooltip + * + * @param text text + * @param color render color + * @return this + */ + public Icon setTooltip(String text, RGB color) + { + tooltipColor = color; + tooltipText = text; + return this; + } + + + public Icon setTooltip(String text) + { + tooltipColor = RGB.WHITE.copy(); + tooltipText = text; + return this; + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + glPushMatrix(); + glPushAttrib(GL_ENABLE_BIT); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + TextureManager.bind(texture); + + RenderUtils.setColor(new RGB(colorTint, enabled ? 1 : 0.3)); + + glBegin(GL_QUADS); + + double left = (tile.x) * 0.125; + double top = (tile.y) * 0.125; + double right = (tile.x + 1) * 0.125; + double bottom = (tile.y + 1) * 0.125; + + int w = 32; + int h = 32; + + Coord c = getRect().getCenter(); + + double x1 = c.x - w / 2; + double y1 = c.y + h / 2; + double x2 = c.x + w / 2; + double y2 = c.y - h / 2; + + glTexCoord2d(left, top); + glVertex3d(x1, y1, 0); + + glTexCoord2d(right, top); + glVertex3d(x2, y1, 0); + + glTexCoord2d(right, bottom); + glVertex3d(x2, y2, 0); + + glTexCoord2d(left, bottom); + glVertex3d(x1, y2, 0); + + glEnd(); + + RenderUtils.setColor(RGB.WHITE); + TextureManager.unbind(); + + glPopAttrib(); + glPopMatrix(); + + boolean onTop = isPanelOnTop(); + boolean hover = isMouseOver(mouse); + + if (enabled && hover && onTop && tooltipText.length() > 0) { + if (System.currentTimeMillis() - mouseEnterTime > 1000) { + renderTooltip(tooltipText, tooltipColor); + } + } else { + mouseEnterTime = System.currentTimeMillis(); + } + } + + + @Override + public void calcChildSizes() + { + setMinSize(new Coord(32, 32)); + rect.setTo(0, 0, minSize.x, minSize.y); + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + +} diff --git a/src/net/tortuga/gui/widgets/display/Image.java b/src/net/tortuga/gui/widgets/display/Image.java new file mode 100644 index 0000000..31974a6 --- /dev/null +++ b/src/net/tortuga/gui/widgets/display/Image.java @@ -0,0 +1,126 @@ +package net.tortuga.gui.widgets.display; + + +import net.tortuga.gui.widgets.Widget; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; + + +/** + * Image from texture + * + * @author MightyPork + */ +public class Image extends Widget { + + private TxQuad texture = null; + + /** Tint color */ + public RGB colorTint = RGB.WHITE.copy(); + + + /** + * Set color tint + * + * @param tint color tint + * @return this + */ + public Image setColor(RGB tint) + { + colorTint.setTo(tint); + return this; + } + + public String tooltipText = ""; + public RGB tooltipColor = RGB.WHITE; + private long mouseEnterTime = 0; + + + /** + * new icon + * + * @param texture texture (px) + * @param textureRect texture rectangle (px) + * @param margin image margins + */ + public Image(TxQuad texture, int margin) { + this.texture = texture; + setMargins(margin, margin, margin, margin); + setMinSize(texture.size); + } + + + /** + * Set tooltip + * + * @param text text + * @param color render color + * @return this + */ + public Image setTooltip(String text, RGB color) + { + tooltipColor = color; + tooltipText = text; + return this; + } + + + public Image setTooltip(String text) + { + tooltipColor = RGB.WHITE.copy(); + tooltipText = text; + return this; + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + RenderUtils.quadTextured(rect, texture, colorTint); + + boolean onTop = isPanelOnTop(); + boolean hover = isMouseOver(mouse); + + if (enabled && hover && onTop && tooltipText.length() > 0) { + if (System.currentTimeMillis() - mouseEnterTime > 1000) { + renderTooltip(tooltipText, tooltipColor); + } + } else { + mouseEnterTime = System.currentTimeMillis(); + } + } + + + @Override + public void calcChildSizes() + { + rect.setTo(0, 0, minSize.x, minSize.y); + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + +} diff --git a/src/net/tortuga/gui/widgets/display/Text.java b/src/net/tortuga/gui/widgets/display/Text.java new file mode 100644 index 0000000..780ff35 --- /dev/null +++ b/src/net/tortuga/gui/widgets/display/Text.java @@ -0,0 +1,266 @@ +package net.tortuga.gui.widgets.display; + + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import net.tortuga.fonts.LoadedFont; +import net.tortuga.gui.widgets.Theme; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.input.Function; +import net.tortuga.util.Align; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; + + +/** + * Passive text label + * + * @author MightyPork + */ +public class Text extends Widget { + + private Function onClickHandler; + + private int align = Align.CENTER; + + private CoordI padding = new CoordI(0, 0); + + private String[] linesRev = null; + + /** Text color */ + public RGB textColor = Theme.TEXT.copy(); + + /** Blur color */ + public RGB blurColor = new RGB(0xffffff, 0.1); + + /** Blur size */ + public int blurSize = 0; + + /** Blur smooth flag */ + public boolean blurSmooth = true; + + + /** + * Set text padding + * + * @param x padding x + * @param y padding y + * @return this + */ + public Text setPadding(int x, int y) + { + padding.setTo(x, y); + return this; + } + + + /** + * Assign on clock handler + * + * @param handler handler + * @return this + */ + public Text addOnClickHandler(Function handler) + { + onClickHandler = handler; + return this; + } + + + /** + * Set text blur + * + * @param color blur color + * @param size blur size + * @param smooth render smooth blur (all iterations / only the outmost) + * @return this + */ + public Text setBlur(RGB color, int size, boolean smooth) + { + this.blurColor.setTo(color); + this.blurSize = size; + this.blurSmooth = smooth; + return this; + } + + + /** + * Set text blur (smooth=true) + * + * @param color blur color + * @param size blur size + * @return this + */ + public Text setBlur(RGB color, int size) + { + this.blurColor.setTo(color); + this.blurSize = size; + this.blurSmooth = true; + return this; + } + + + /** + * new text + * + * @param text widget text + * @param font render font + */ + public Text(String text, LoadedFont font) { + setText(text); + setMargins(2, 0, 2, 0); + setFont(font); + } + + + /** + * new text + * + * @param text widget text + */ + public Text(String text) { + setText(text); + setMargins(2, 0, 2, 0); + } + + + @Override + public void calcChildSizes() + { + Coord oldms = getMinSize().copy(); + + double max = 0; + for (String s : linesRev) { + max = Math.max(max, font.getWidth(s)); + } + + setMinSize(new Coord(max, font.getHeight() * linesRev.length)); + + if (minSize.x < oldms.x) minSize.x = oldms.x; + if (minSize.y < oldms.y) minSize.y = oldms.y; + rect.setTo(0, 0, minSize.x + padding.x * 2, minSize.y + padding.y * 2); + } + + + @Override + public void onBlur() + {} + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + if (isMouseOver(pos) && down) clicked = true; + if (!down) { + if (clicked && isMouseOver(pos)) { + clicked = false; + if (onClickHandler != null) return onClickHandler.run(this) ? this : null; + } + clicked = false; + } + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + int i = 0; + for (String s : linesRev) { + Coord txtPos = rect.getCenter(); + + switch (align) { + case Align.CENTER: + txtPos = rect.getCenter(); + break; + + case Align.LEFT: + txtPos = rect.getCenter(); + txtPos.x = rect.getMin().x + padding.x; + break; + + case Align.RIGHT: + txtPos = rect.getCenter(); + txtPos.x = rect.getMax().x - padding.x; + break; + } + + txtPos.sub_ip(2, font.getHeight() * linesRev.length / 2).add_ip(0, font.getHeight() * i).round_ip(); + + if (blurSize == 0) { + font.draw(txtPos, s, textColor, align); + } else { + font.drawFuzzy(txtPos, text, align, textColor, blurColor, blurSize, blurSmooth); + } + + i++; + + } + + // debug white rect showing the bounds. +// RenderUtils.setColor(new RGB(1,1,1,0.2)); +// RenderUtils.quadAbsCoord(rect.x1(), rect.y1(), rect.x2(), rect.y2()); +// RenderUtils.setColor(new RGB(1,1,1)); + } + + + /** + * Set text color + * + * @param color color + * @return this + */ + public Text setColorText(RGB color) + { + textColor = color; + return this; + } + + + @Override + public Text setText(String text) + { + super.setText(text); + + linesRev = text.split("\n"); + List l = Arrays.asList(linesRev); + Collections.reverse(l); + + return this; + } + + + /** + * Set how the text should be aligned if minWidth is higher than text's + * width. + * + * @param align -1 left, 0 center, 1 right; Use constant from Align class. + * @return this + */ + public Text setTextAlign(int align) + { + this.align = align; + return this; + } + +} diff --git a/src/net/tortuga/gui/widgets/display/TextDouble.java b/src/net/tortuga/gui/widgets/display/TextDouble.java new file mode 100644 index 0000000..bb01417 --- /dev/null +++ b/src/net/tortuga/gui/widgets/display/TextDouble.java @@ -0,0 +1,134 @@ +package net.tortuga.gui.widgets.display; + + +import net.tortuga.gui.widgets.Theme; +import net.tortuga.gui.widgets.Widget; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; + + +/** + * Passive text label + * + * @author MightyPork + */ +public class TextDouble extends Widget { + + private String textLeft = ""; + private String textRight = ""; + + /** Text color left */ + public RGB colorLeft = Theme.TEXT.copy(); + /** Text color right */ + public RGB colorRight = Theme.TEXT.copy(); + + + /** + * new text + * + * @param width min width + */ + public TextDouble(int width) { + setMargins(2, 0, 2, 0); + setMinWidth(width); + } + + + public TextDouble setTextLeft(String text, RGB color) + { + textLeft = text; + colorLeft = color; + return this; + } + + + public TextDouble setTextRight(String text, RGB color) + { + textRight = text; + colorRight = color; + return this; + } + + + @Override + public void calcChildSizes() + { + Coord oldms = getMinSize().copy(); + setMinSize(new Coord(oldms.x, font.getHeight())); + if (minSize.x < oldms.x) minSize.x = oldms.x; + if (minSize.y < oldms.y) minSize.y = oldms.y; + rect.setTo(0, 0, minSize.x, minSize.y); + } + + + @Override + public void onBlur() + {} + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + Coord leftP; + leftP = rect.getCenter(); + leftP.x = rect.getMin().x; + + Coord rightP; + + rightP = rect.getCenter(); + rightP.x = rect.getMax().x; + + leftP.sub_ip(2, font.getHeight() / 2).round_ip(); + font.draw(leftP, textLeft, colorLeft, -1); + + rightP.sub_ip(2, font.getHeight() / 2).round_ip(); + font.draw(rightP, textRight, colorRight, 1); + } + + + public void eraseTexts() + { + textLeft = ""; + textRight = ""; + } + + + public TextDouble setTexts(String left, String right) + { + this.textLeft = left; + this.textRight = right; + return this; + } + + + public TextDouble setColors(RGB left, RGB right) + { + this.colorLeft = left; + this.colorRight = right; + return this; + } +} diff --git a/src/net/tortuga/gui/widgets/display/TextWithBackground.java b/src/net/tortuga/gui/widgets/display/TextWithBackground.java new file mode 100644 index 0000000..41ac7f8 --- /dev/null +++ b/src/net/tortuga/gui/widgets/display/TextWithBackground.java @@ -0,0 +1,52 @@ +package net.tortuga.gui.widgets.display; + + +import net.tortuga.fonts.LoadedFont; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; + + +public class TextWithBackground extends Text { + + private RGB bgColor = new RGB(0x479EF5); + + + public TextWithBackground setBackgroundColor(RGB color) + { + this.bgColor = color; + return this; + } + + + public TextWithBackground(String text) { + super(text); + } + + + public TextWithBackground(String text, LoadedFont font) { + super(text, font); + } + + + @Override + public void render(Coord mouse) + { + boolean over = isMouseOver(mouse); + + RGB color = new RGB(bgColor, 0.3); + + if (over) { + color = new RGB(bgColor, 0.9); + } + + RenderUtils.setColor(color); + RenderUtils.quadSize(rect.getMin().x, rect.getMin().y, rect.getSize().x, rect.getSize().y); + + RenderUtils.setColor(RGB.WHITE); + + super.render(mouse); + } + +} diff --git a/src/net/tortuga/gui/widgets/input/Button.java b/src/net/tortuga/gui/widgets/input/Button.java new file mode 100644 index 0000000..9bfe7e0 --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/Button.java @@ -0,0 +1,228 @@ +package net.tortuga.gui.widgets.input; + + +import static net.tortuga.gui.widgets.EColorRole.*; +import net.tortuga.fonts.Fonts; +import net.tortuga.fonts.LoadedFont; +import net.tortuga.gui.widgets.ETheme; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.input.Function; +import net.tortuga.sounds.Effects; +import net.tortuga.textures.Tx; +import net.tortuga.util.Align; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; + + +/** + * Clickable button. + * + * @author MightyPork + */ +public class Button extends Widget { + + private Function onClickHandler; + + public boolean lastRenderHover = false; + + public double borderWidth = 2; + public boolean showBorder = true; + public double paddingX = 12;//10 + public double paddingY = 5;//4 + + public String tooltipText = ""; + public RGB tooltipColor = RGB.WHITE; + + + public void addOnClickHandler(Function handler) + { + onClickHandler = handler; + } + + + public Button setPadding(double x, double y) + { + paddingX = x; + paddingY = y; + return this; + } + + + public Button setBorderSize(double width) + { + borderWidth = width; + showBorder = true; + return this; + } + + + public Button enableBorder(boolean flag) + { + showBorder = flag; + if (!flag) borderWidth = 0; + return this; + } + + + /** + * new button + * + * @param id widget id + * @param text widget text + * @param font render font + */ + public Button(int id, String text, LoadedFont font) { + setId(id); + setText(text); + setFont(font); + setTheme(ETheme.BUTTON); + setMinSize(110, 0); + } + + + /** + * new button + * + * @param id widget id + * @param text widget text + */ + public Button(int id, String text) { + this(id, text, Fonts.gui); + } + + + /** + * new button + * + * @param id widget id + */ + public Button(int id) { + setId(id); + } + + + /** + * Set tooltip + * + * @param text text + * @param color render color + * @return this + */ + public Button setTooltip(String text, RGB color) + { + tooltipColor = color; + tooltipText = text; + return this; + } + + + public Button setTooltip(String text) + { + tooltipColor = RGB.WHITE.copy(); + tooltipText = text; + return this; + } + + /** Borders - L,R,T,B */ + public boolean bdrs[] = { true, true, true, true }; + + private long mouseEnterTime = 0; + + + protected void renderBase(Coord mouse) + { + if (!isVisible()) return; + + boolean onTop = isPanelOnTop(); + + boolean hover = isMouseOver(mouse); + + int ofs = selected || clicked ? 1 : hover ? 2 : 0; + RenderUtils.quadTexturedFrame(rect.round(), Tx.BTN_SMALL, ofs, enabled ? RGB.WHITE : new RGB(RGB.WHITE, 0.3)); + + if (enabled && hover && onTop && tooltipText.length() > 0) { + if (System.currentTimeMillis() - mouseEnterTime > 1000) { + renderTooltip(tooltipText, tooltipColor); + } + } else { + mouseEnterTime = System.currentTimeMillis(); + } + + lastRenderHover = hover; + + //RenderUtils.quadBorder(rect.grow(1, 1), 1, RGB.ORANGE, null); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + renderBase(mouse); + boolean hover = isMouseOver(mouse); + + Coord txtCenterPos = rect.getCenter().sub(3, font.getHeight() / 2 + 2).round(); + font.drawFuzzy(txtCenterPos, text, Align.CENTER, getColor(FG, mouse, hover), getColor(SHADOW, mouse, hover), 2, false); + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + if (selected || !enabled) return null; + if (button != 0) return null; + if (!isMouseOver(pos)) { + clicked = false; + return null; + } + if (down == true) { + clicked = true; + } else { + if (clicked) { + Effects.play("gui.button.dialog"); + + clicked = false; + + if (onClickHandler != null) return onClickHandler.run() ? this : null; + + return this; + } + } + return null; + } + + + @Override + public void onBlur() + { + clicked = false; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + Coord oldms = getMinSize().copy(); + setMinSize(new Coord(font.getWidth(text) + borderWidth * 2 + paddingX * 2, font.getHeight() + borderWidth * 2 + paddingY * 2)); + if (minSize.x < oldms.x) minSize.x = oldms.x; + if (minSize.y < oldms.y) minSize.y = oldms.y; + rect.setTo(0, 0, minSize.x, minSize.y); + } + +} diff --git a/src/net/tortuga/gui/widgets/input/ButtonIcon.java b/src/net/tortuga/gui/widgets/input/ButtonIcon.java new file mode 100644 index 0000000..9a4723f --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/ButtonIcon.java @@ -0,0 +1,87 @@ +package net.tortuga.gui.widgets.input; + + +import net.tortuga.textures.TxQuad; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.HSV; +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Button with icon + * + * @author MightyPork + */ +public class ButtonIcon extends Button { + + private TxQuad txq = null; + + private RGB tint = RGB.WHITE; + + + /** + * Set color tint + * + * @param tint color tint + * @return this + */ + public ButtonIcon setColor(RGB tint) + { + tint.setTo(tint); + return this; + } + + + /** + * new button + * + * @param id widget id + * @param txquad texture quad + */ + public ButtonIcon(int id, TxQuad txquad, RGB tint) { + super(id); + setId(id); + this.tint = tint; + this.txq = txquad; + setPadding(8, 8); + setMargins(3, 3, 3, 3); + setMinSize(0, 0); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + renderBase(mouse); + + Rect iconRect = new Rect(rect.getCenter(), rect.getCenter()).grow_ip(txq.size.x / 2, txq.size.y / 2).round_ip(); + + HSV color = tint.toHSV(); + + if (clicked || selected) { + color.v *= 0.8; + } else if (isMouseOver(mouse)) { + color.v *= 1.2; + } + + color.norm(); + + RenderUtils.quadTextured(iconRect, txq, new RGB(color.toRGB(), enabled ? 1 : 0.3)); + } + + + @Override + public void calcChildSizes() + { + Coord oldms = getMinSize().copy(); + setMinSize(new Coord(txq.size.x + borderWidth * 2 + paddingX * 2, txq.size.y + borderWidth * 2 + paddingY * 2)); + if (minSize.x < oldms.x) minSize.x = oldms.x; + if (minSize.y < oldms.y) minSize.y = oldms.y; + rect.setTo(0, 0, minSize.x, minSize.y); + } + +} diff --git a/src/net/tortuga/gui/widgets/input/ButtonVertical.java b/src/net/tortuga/gui/widgets/input/ButtonVertical.java new file mode 100644 index 0000000..7e05442 --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/ButtonVertical.java @@ -0,0 +1,86 @@ +package net.tortuga.gui.widgets.input; + + +import static net.tortuga.gui.widgets.EColorRole.*; +import net.tortuga.fonts.LoadedFont; +import net.tortuga.util.Align; + +import org.lwjgl.opengl.GL11; + +import com.porcupine.coord.Coord; + + +/** + * Vertical (left tab) button + * + * @author MightyPork + */ +public class ButtonVertical extends Button { + + /** + * new button v + * + * @param id widget id + * @param text widget text + * @param font render font + */ + public ButtonVertical(int id, String text, LoadedFont font) { + super(id, text, font); + } + + + /** + * new button v + * + * @param id widget id + * @param text widget text + */ + public ButtonVertical(int id, String text) { + super(id, text); + setMinSize(0, 110); + } + + + /** + * new button v + * + * @param id widget id + */ + public ButtonVertical(int id) { + super(id); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + renderBase(mouse); + + GL11.glPushMatrix(); + + Coord txtCenterPos = rect.getCenter().add(font.getHeight() / 2 + 2, -3); + + GL11.glTranslated(txtCenterPos.x, txtCenterPos.y, 0); + + GL11.glRotated(90, 0, 0, 1); + + font.draw(Coord.ZERO, text, getColor(FG, mouse), Align.CENTER); + + GL11.glPopMatrix(); + } + + + @Override + public void calcChildSizes() + { + Coord oldms = getMinSize().copy(); + double w = font.getHeight() + borderWidth * 2 + paddingX * 2; + double h = font.getWidth(text) + borderWidth * 2 + paddingY * 2; + setMinSize(new Coord(w, h)); + if (minSize.x < oldms.x) minSize.x = oldms.x; + if (minSize.y < oldms.y) minSize.y = oldms.y; + rect.setTo(0, 0, minSize.x, minSize.y); + } + +} diff --git a/src/net/tortuga/gui/widgets/input/Checkbox.java b/src/net/tortuga/gui/widgets/input/Checkbox.java new file mode 100644 index 0000000..3fb5eca --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/Checkbox.java @@ -0,0 +1,175 @@ +package net.tortuga.gui.widgets.input; + + +import static net.tortuga.gui.widgets.EColorRole.*; +import net.tortuga.fonts.Fonts; +import net.tortuga.fonts.LoadedFont; +import net.tortuga.gui.widgets.ETheme; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.sounds.Effects; +import net.tortuga.textures.Tx; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.Align; +import net.tortuga.util.RenderUtils; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Clcikable button. + * + * @author MightyPork + */ +public class Checkbox extends Widget { + + public boolean checked = false; + protected int txtDist = 0; + + + public Checkbox setChecked(boolean checked) + { + setChecked_do(checked); + return this; + } + + + public void setChecked_do(boolean checked) + { + this.checked = checked; + } + + + public boolean isChecked() + { + return checked; + } + + + /** + * new ckbox + * + * @param id widget id + * @param text widget text + * @param font render font + */ + public Checkbox(int id, String text, LoadedFont font) { + setId(id); + setText(text); + setFont(font); + setTheme(ETheme.WIDGET); + } + + + /** + * new ckbox + * + * @param id widget id + * @param text widget text + */ + public Checkbox(int id, String text) { + this(id, text, Fonts.gui); + } + + + protected Coord getBoxSize() + { + return new Coord(58, 28); + } + + + protected Rect getBoxRect() + { + Coord size = getBoxSize(); + int imgW = (int) size.x; + int imgH = (int) size.y; + + Coord left = rect.getCenterLeft().sub(-2, imgH / 2).round(); + return new Rect(left, left.add(imgW, imgH)); + } + + + public void renderBase(Coord mouse) + { + if (!isVisible()) return; + + Coord txtCenterPos = getBoxRect().getCenterRight().add(txtDist, -font.getHeight() / 2).round(); + font.draw(txtCenterPos, text, getColor(FG, mouse), Align.LEFT); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + renderBase(mouse); + + Rect box = getBoxRect().round(); + TxQuad txq; + + if (checked) { + txq = Tx.CKBOX_ON; + } else { + txq = Tx.CKBOX_OFF; + } + + RenderUtils.quadTextured(box, txq); + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + if (button != 0) return null; + if (!isMouseOver(pos)) { + clicked = false; + return null; + } + if (down == true) { + clicked = true; + } else { + if (clicked) { + Effects.play("gui.switch"); + + clicked = false; + setChecked(!isChecked()); + return this; + } + } + return null; + } + + + @Override + public void onBlur() + { + clicked = false; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + Coord oldms = getMinSize().copy(); + setMinSize(new Coord(font.getWidth(text) + getBoxSize().x + this.txtDist, Math.max(font.getHeight(), getBoxSize().y))); + if (minSize.x < oldms.x) minSize.x = oldms.x; + if (minSize.y < oldms.y) minSize.y = oldms.y; + rect.setTo(0, 0, minSize.x, minSize.y); + } + +} diff --git a/src/net/tortuga/gui/widgets/input/RadioButton.java b/src/net/tortuga/gui/widgets/input/RadioButton.java new file mode 100644 index 0000000..52a4997 --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/RadioButton.java @@ -0,0 +1,119 @@ +package net.tortuga.gui.widgets.input; + + +import java.util.HashSet; + +import net.tortuga.fonts.LoadedFont; +import net.tortuga.textures.Tx; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.RenderUtils; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Radio button. + * + * @author MightyPork + */ +public class RadioButton extends Checkbox { + + /** + * Radio group. + * + * @author MightyPork + */ + public static class RadioGroup extends HashSet { + } + + + /** + * Build new radiobutton group. + * + * @return group list. + */ + public static RadioGroup newGroup() + { + return new RadioGroup(); + } + + private RadioGroup group = null; + + + /** + * Assign radio button group + * + * @param groupList group list + * @return this + */ + public RadioButton setGroup(RadioGroup groupList) + { + this.group = groupList; + group.add(this); + return this; + } + + + /** + * Radio button + * + * @param id widget ID + * @param text + */ + public RadioButton(int id, String text) { + super(id, text); + txtDist = 5; + } + + + /** + * Radio button + * + * @param id widget id + * @param text widget text + * @param font render font + */ + public RadioButton(int id, String text, LoadedFont font) { + super(id, text, font); + txtDist = 5; + } + + + @Override + public Checkbox setChecked(boolean checked) + { + if (checked == false) return this; // no unchecking here. + for (RadioButton rb : group) + rb.setChecked_do(false); + setChecked_do(true); + return this; + } + + + @Override + protected Coord getBoxSize() + { + return new Coord(22, 22); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + renderBase(mouse); + + Rect box = getBoxRect(); + TxQuad txq; + + if (checked) { + txq = Tx.RADIO_ON; + } else { + txq = Tx.RADIO_OFF; + } + + RenderUtils.quadTextured(box, txq); + } +} diff --git a/src/net/tortuga/gui/widgets/input/ScrollbarBase.java b/src/net/tortuga/gui/widgets/input/ScrollbarBase.java new file mode 100644 index 0000000..3f1f209 --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/ScrollbarBase.java @@ -0,0 +1,203 @@ +package net.tortuga.gui.widgets.input; + + +import net.tortuga.gui.widgets.IScrollable; +import net.tortuga.gui.widgets.IScrollbar; +import net.tortuga.gui.widgets.Widget; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; +import com.porcupine.math.Calc; + + +/** + * Clcikable button. + * + * @author MightyPork + */ +public abstract class ScrollbarBase extends Widget implements IScrollbar { + + public static final int DEFAULT_WIDTH = 14; + + protected int bdr = 2; + + protected double value = 0; + + protected IScrollable scrollable; + + + /** + * Set scrollable element + * + * @param scrollable scrollable element + * @return this + */ + public ScrollbarBase setScrollable(IScrollable scrollable) + { + this.scrollable = scrollable; + scrollable.onScrollbarConnected(this); + return this; + } + + + protected double getContentSize() + { + if (scrollable == null) return 100; + return scrollable.getContentSize(); + } + + + protected double getViewSize() + { + if (scrollable == null) return 30; + return scrollable.getViewSize(); + } + + + protected abstract double getTrackSize(); + + + protected double getHandleSize() + { + double hs = (getViewSize() / getContentSize()) * getTrackSize(); + if (hs < 41) hs = 41; + if (hs > getTrackSize()) hs = getTrackSize(); + return hs; + } + + + protected abstract Rect getHandleLine(); + + + protected abstract Rect getRidgeLine(); + + + @Override + public abstract void render(Coord mouse); + + protected boolean isDragging = false; + protected Coord posDragStart; + protected double valueDragStart = 0; + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + if (button != 0) return null; + if (!isDragging && !isMouseOver(pos)) { + return null; + } + if (getContentSize() <= getViewSize()) return null; + if (down == true) { + double size = getHandleSize(); + + if (pos.distTo(getHandleLine().getCenter()) > size / 2) { + Rect ridge = getRidgeLine(); + + double abspos = getDistInDir1(ridge, pos); + double rsize = ridge.getSize().size(); + + double vlast = value; + value = Calc.clampd(abspos / rsize, 0, 1); + + if (value != vlast && scrollable != null) { + scrollable.onScrollbarChange(value); + } + } + + isDragging = true; + posDragStart = pos; + valueDragStart = value; + } else { + // up. + isDragging = false; + } + return null; + } + + + protected abstract double getDistInDir1(Rect ridge, Coord to); + + + protected abstract double getDistInDir2(Coord from, Coord to); + + + @Override + public void handleStaticInputs(Coord pos) + { + if (isDragging) { + double vlast = value; + value = valueDragStart + (1 / (getTrackSize() - getHandleSize())) * getDistInDir2(posDragStart, pos); + value = Calc.clampd(value, 0, 1); + if (value != vlast && scrollable != null) { + scrollable.onScrollbarChange(value); + } + } + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + if (isMouseOver(pos)) { + return onScrollDelegate(scroll); + } + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + rect.setTo(0, 0, minSize.x, minSize.y); + } + + + /** + * Handler fro scroll wheel + * + * @param scroll scroll steps + * @return this + */ + @Override + public Widget onScrollDelegate(int scroll) + { + if (getContentSize() <= getViewSize()) return null; + double vlast = value; + value = Calc.clampd(value - scroll * (getViewSize() / getContentSize()) * 0.2, 0, 1); + if (value != vlast && scrollable != null) scrollable.onScrollbarChange(value); + return this; + } + + + /** + * Get scrollbar value + * + * @return value + */ + @Override + public double getValue() + { + return value; + } + + + /** + * Set scrollbar value + * + * @param value value + */ + @Override + public void setValue(double value) + { + this.value = Calc.clampd(value, 0, 1); + } + +} diff --git a/src/net/tortuga/gui/widgets/input/ScrollbarH.java b/src/net/tortuga/gui/widgets/input/ScrollbarH.java new file mode 100644 index 0000000..8b7b145 --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/ScrollbarH.java @@ -0,0 +1,144 @@ +package net.tortuga.gui.widgets.input; + + +import net.tortuga.gui.widgets.IScrollable; +import net.tortuga.textures.Tx; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Horizontal scrollbar + * + * @author MightyPork + */ +public class ScrollbarH extends ScrollbarBase { + + /** + * new scrollbar + * + * @param width scrollbar width + * @param height scrollbar height + */ + public ScrollbarH(int width, int height) { + setMinSize(width, height); + } + + + /** + * new scrollbar + * + * @param width scrollbar width + */ + public ScrollbarH(int width) { + setMinSize(width, DEFAULT_WIDTH); + } + + + /** + * new scrollbar + * + * @param width widget width + * @param scrollable scrollable object + */ + public ScrollbarH(int width, IScrollable scrollable) { + this(width, DEFAULT_WIDTH, scrollable); + } + + + /** + * new scrollbar + * + * @param width scrollbar width + * @param height scrollbar height + * @param scrollable scrollable element + */ + public ScrollbarH(int width, int height, IScrollable scrollable) { + setMinSize(width, height); + setScrollable(scrollable); + } + + + @Override + protected double getTrackSize() + { + return getSize().x - bdr * 2; + } + + + @Override + protected Rect getHandleLine() + { + double track = getTrackSize(); + double handle = getHandleSize(); + + double fromMin = rect.getMin().x + bdr + (track - handle) * value; + + Coord min = new Coord(fromMin, rect.getCenter().y); + Coord max = min.add(getHandleSize(), 0); + + return new Rect(min, max).round(); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + int handleH = 19; + int trackH = 8; + + Rect track = rect.getAxisH().grow_ip(0, trackH / 2).round(); + TxQuad txTrack = Tx.SCROLL_H_TRACK; + + Rect handle = getHandleLine().grow_ip(0, handleH / 2).round(); + TxQuad txHandle; + + Coord handleCenter = getHandleLine().getCenter().round(); + Rect decor = new Rect(handleCenter, handleCenter).grow_ip(25D / 2D, handleH / 2D).round(); + TxQuad txDecor; + + if (isMouseOver(mouse) || isDragging) { + txHandle = Tx.SCROLL_H_HANDLE_HOVER; + txDecor = Tx.SCROLL_H_DOTS_HOVER; + } else { + txHandle = Tx.SCROLL_H_HANDLE; + txDecor = Tx.SCROLL_H_DOTS; + } + + RGB dye = new RGB(RGB.WHITE, getViewSize() < getContentSize() ? 0.9 : 0.7); + + RenderUtils.quadTexturedStretchH(track, txTrack, 11, dye); + + if (getViewSize() < getContentSize()) { + RenderUtils.quadTexturedStretchH(handle, txHandle, 6); + RenderUtils.quadTextured(decor, txDecor); + } + } + + + @Override + protected double getDistInDir1(Rect ridge, Coord to) + { + return to.x - ridge.getMin().x; + } + + + @Override + protected double getDistInDir2(Coord from, Coord to) + { + return to.x - from.x; + } + + + @Override + protected Rect getRidgeLine() + { + return rect.getAxisH().grow_ip(-(2 + getHandleSize() / 2), 0); + } +} diff --git a/src/net/tortuga/gui/widgets/input/ScrollbarV.java b/src/net/tortuga/gui/widgets/input/ScrollbarV.java new file mode 100644 index 0000000..f882c12 --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/ScrollbarV.java @@ -0,0 +1,143 @@ +package net.tortuga.gui.widgets.input; + + +import net.tortuga.gui.widgets.IScrollable; +import net.tortuga.textures.Tx; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Clcikable button. + * + * @author MightyPork + */ +public class ScrollbarV extends ScrollbarBase { + + /** + * new scrollbar + * + * @param width scrollbar width + * @param height scrollbar height + */ + public ScrollbarV(int width, int height) { + setMinSize(width, height); + } + + + /** + * new scrollbar + * + * @param height scrollbar height + */ + public ScrollbarV(int height) { + setMinSize(DEFAULT_WIDTH, height); + } + + + /** + * new scrollbar + * + * @param height widget height + * @param scrollable scrollable object + */ + public ScrollbarV(int height, IScrollable scrollable) { + this(DEFAULT_WIDTH, height, scrollable); + } + + + /** + * new scrollbar + * + * @param width scrollbar width + * @param height scrollbar height + * @param scrollable scrollable element + */ + public ScrollbarV(int width, int height, IScrollable scrollable) { + setMinSize(width, height); + setScrollable(scrollable); + } + + + @Override + protected double getTrackSize() + { + return getSize().y - bdr * 2; + } + + + @Override + protected Rect getHandleLine() + { + double track = getTrackSize(); + double handle = getHandleSize(); + + double fromMin = rect.getMin().y + bdr + track - (track - handle) * value - handle; + + Coord min = new Coord(rect.getCenter().x, fromMin); + Coord max = min.add(0, getHandleSize()); + + return new Rect(min, max).round(); + } + + + @Override + protected Rect getRidgeLine() + { + return rect.getAxisV().grow_ip(0, -(2 + getHandleSize() / 2)); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + int handleW = 19; + int trackW = 8; + + Rect track = rect.getAxisV().round().grow_ip(trackW / 2, 0); + TxQuad txTrack = Tx.SCROLL_V_TRACK; + Rect handle = getHandleLine().grow_ip(handleW / 2, 0).round(); + TxQuad txHandle; + + Coord handleCenter = getHandleLine().getCenter().round(); + Rect decor = new Rect(handleCenter, handleCenter).grow_ip(handleW / 2D, 25 / 2D).round(); + TxQuad txDecor; + + if (isMouseOver(mouse) || isDragging) { + txHandle = Tx.SCROLL_V_HANDLE_HOVER; + txDecor = Tx.SCROLL_V_DOTS_HOVER; + } else { + txHandle = Tx.SCROLL_V_HANDLE; + txDecor = Tx.SCROLL_V_DOTS; + } + + RGB dye = new RGB(RGB.WHITE, getViewSize() < getContentSize() ? 0.9 : 0.7); + + RenderUtils.quadTexturedStretchV(track, txTrack, 11, dye); + + if (getViewSize() < getContentSize()) { + RenderUtils.quadTexturedStretchV(handle, txHandle, 6); + RenderUtils.quadTextured(decor, txDecor); + } + } + + + @Override + protected double getDistInDir1(Rect ridge, Coord to) + { + return ridge.getMax().y - to.y; + } + + + @Override + protected double getDistInDir2(Coord from, Coord to) + { + return from.y - to.y; + } +} diff --git a/src/net/tortuga/gui/widgets/input/Slider.java b/src/net/tortuga/gui/widgets/input/Slider.java new file mode 100644 index 0000000..6f5bcf0 --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/Slider.java @@ -0,0 +1,186 @@ +package net.tortuga.gui.widgets.input; + + +import net.tortuga.gui.panels.PanelGui; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.textures.Tx; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; +import com.porcupine.math.Calc; + + +/** + * Clcikable button. + * + * @author MightyPork + */ +public class Slider extends Widget { + + protected double value = 0; + private int handleH = 24; + private int paddingH = 0; + private int ingapH = paddingH + handleH / 2 - 4; + protected int trackH = 14; + protected int fillH = 10; + + + /** + * new scrollbar + * + * @param width scrollbar width + * @param value slider value + */ + public Slider(int width, double value) { + setMinSize(width, handleH + 4); + setValue(value); + } + + + private double getGrooveWidth() + { + return getSize().x - paddingH * 2 - ingapH * 2; + } + + + protected Coord getHandlePos() + { + double GW = getGrooveWidth(); + return new Coord(rect.getMin().x + paddingH + ingapH + (GW) * value, rect.getCenter().y); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + Coord handle = getHandlePos(); + + // rect center left and right + Coord cL = rect.getCenterLeft(); + Coord cR = rect.getCenterRight(); + + // prepare textured rects + Rect track = new Rect(cL.sub(0, trackH / 2), cR.add(0, trackH / 2)).round(); + Rect fill = new Rect(cL.sub(-3, fillH / 2), handle.add(0, fillH / 2)).round(); + + TxQuad trackUVs = Tx.SLIDER_TRACK; + TxQuad fillUVs = Tx.SLIDER_FILL; + TxQuad txHandle = Tx.SLIDER_HANDLE; + + RenderUtils.quadTexturedStretchH(track, trackUVs, 7); + RenderUtils.quadTexturedStretchH(fill, fillUVs, 7, new RGB(0x00b19a)); + + // draw handle + Rect handleRect = new Rect(handle.sub(12, 12), handle.add(12, 12)).round(); + RenderUtils.quadTextured(handleRect, txHandle); + } + + private boolean isDragging = false; + private Coord posDragStart; + private double valueDragStart = 0; + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + if (button != 0) return null; + if (!isDragging && !isMouseOver(pos)) { + return null; + } + + if (down == true) { + if (pos.distTo(getHandlePos()) > handleH / 2) { + Coord cL = rect.getCenterLeft(); + Coord cR = rect.getCenterRight(); + Rect ridge = new Rect(cL.add(7, 0), cR.sub(7, 0)); + + double abspos = pos.x - ridge.x1(); + double rsize = ridge.getSize().x; + + double vlast = value; + value = Calc.clampd(abspos / rsize, 0, 1); + if (value != vlast) { + ((PanelGui) getPanel()).actionPerformed(this); + } + } + + isDragging = true; + posDragStart = pos; + valueDragStart = value; + } else { + // up. + isDragging = false; + } + return null; + } + + + @Override + public void handleStaticInputs(Coord pos) + { + if (isDragging) { + double vlast = value; + value = valueDragStart - (1 / getGrooveWidth()) * (posDragStart.x - pos.x); + value = Calc.clampd(value, 0, 1); + if (value != vlast) { + ((PanelGui) getPanel()).actionPerformed(this); + } + } + } + + + @Override + public void onBlur() + {} + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + if (isMouseOver(pos)) { + return onScrollDelegate(scroll); + } + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + rect.setTo(0, 0, minSize.x, minSize.y); + } + + + public Widget onScrollDelegate(int scroll) + { + double vlast = value; + value = Calc.clampd(value + scroll * 0.1, 0, 1); + if (value != vlast) ((PanelGui) getPanel()).actionPerformed(this); + return this; + } + + + public double getValue() + { + return value; + } + + + public void setValue(double value) + { + this.value = Calc.clampd(value, 0, 1); + } + +} diff --git a/src/net/tortuga/gui/widgets/input/SliderGalvanic.java b/src/net/tortuga/gui/widgets/input/SliderGalvanic.java new file mode 100644 index 0000000..af50bfa --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/SliderGalvanic.java @@ -0,0 +1,71 @@ +package net.tortuga.gui.widgets.input; + + +import net.tortuga.textures.Tx; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Clcikable button. + * + * @author MightyPork + */ +public class SliderGalvanic extends Slider { + + /** + * new scrollbar + * + * @param width scrollbar width + * @param value slider value + */ + public SliderGalvanic(int width, double value) { + super(width, (1 + value) / 2D); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + Coord handle = getHandlePos(); + + // rect center left and right + Coord cL = rect.getCenterLeft(); + Coord cR = rect.getCenterRight(); + + // prepare textured rects + Rect track = new Rect(cL.sub(0, trackH / 2), cR.add(0, trackH / 2)).round(); + Rect fill = new Rect(rect.getCenter().sub(-3, fillH / 2), handle.add(0, fillH / 2)).round(); + + TxQuad trackUVs = Tx.SLIDER_TRACK; + TxQuad fillUVs = Tx.SLIDER_FILL2; + TxQuad txHandle = Tx.SLIDER_HANDLE; + + RenderUtils.quadTexturedStretchH(track, trackUVs, 7); + RenderUtils.quadTexturedStretchH(fill, fillUVs, 7, new RGB(0x00b19a)); + + // draw handle + Rect handleRect = new Rect(handle.sub(12, 12), handle.add(12, 12)).round(); + RenderUtils.quadTextured(handleRect, txHandle); + } + + + @Override + public void setValue(double value) + { + super.setValue((1 + value) / 2D); + } + + + @Override + public double getValue() + { + return (-0.5D + super.getValue()) * 2; + } +} diff --git a/src/net/tortuga/gui/widgets/input/TextInput.java b/src/net/tortuga/gui/widgets/input/TextInput.java new file mode 100644 index 0000000..fe6bc4a --- /dev/null +++ b/src/net/tortuga/gui/widgets/input/TextInput.java @@ -0,0 +1,199 @@ +package net.tortuga.gui.widgets.input; + + +import static net.tortuga.gui.widgets.EColorRole.*; +import net.tortuga.fonts.FontManager; +import net.tortuga.fonts.LoadedFont; +import net.tortuga.gui.widgets.ETheme; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.util.Align; +import net.tortuga.util.RenderUtils; + +import org.lwjgl.input.Keyboard; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.util.StringUtils; + + +/** + * Clcikable button. + * + * @author MightyPork + */ +public class TextInput extends Widget { + + public static final String CHARS_ALL = FontManager.Glyphs.all; + public static final String CHARS_ALNUM = FontManager.Glyphs.alnum; + public static final String CHARS_BASIC = FontManager.Glyphs.basic; + public static final String CHARS_EMAIL = FontManager.Glyphs.alnum_nospace + ".@+-_"; + public static final String CHARS_FILENAME = FontManager.Glyphs.alnum + ".,+-'!_&%#@()[]{}"; + public static final String CHARS_NUMBERS = "0123456789"; + public static final String CHARS_NUMBERS_FLOAT = "0123456789.-"; + public static final String CHARS_USERNAME = FontManager.Glyphs.basic; + + public String allowedChars = CHARS_BASIC; + + public double borderWidth = 2; + public double paddingX = 6; + public double paddingY = 4; + + public boolean passwordMode = false; + + + /** + * new input + * + * @param id widget id + * @param text widget text + * @param font render font + */ + public TextInput(int id, String text, LoadedFont font) { + setId(id); + setText(text); + setFont(font); + setTheme(ETheme.WIDGET); + } + + + /** + * new input + * + * @param id widget id + * @param text widget text + */ + public TextInput(int id, String text) { + setId(id); + setText(text); + setTheme(ETheme.WIDGET); + } + + + @Override + public void calcChildSizes() + { + Coord oldms = getMinSize().copy(); + // + borderWidth * 2 + paddingX * 2 + setMinSize(new Coord(oldms.x, font.getHeight() + borderWidth * 2 + paddingY * 2)); + if (minSize.x < oldms.x) minSize.x = oldms.x; + if (minSize.y < oldms.y) minSize.y = oldms.y; + rect.setTo(0, 0, minSize.x, minSize.y); + } + + + private String getShownText(String text) + { + if (!passwordMode) return text; + return StringUtils.passwordify(text); + } + + + @Override + public void onBlur() + {} + + + @Override + public void onFocus() + { + super.onFocus(); + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + if (focused && enabled && down && chr != 0) { + if (key == Keyboard.KEY_BACK || key == Keyboard.KEY_DELETE) { + if (text.length() > 0) { + text = text.substring(0, text.length() - 1); + return this; + } + } + if (allowedChars == null || allowedChars.contains("" + chr)) { + if (font.getWidth(getShownText(text + chr) + "|") <= getMinSize().x - borderWidth * 2 - paddingX * 2) { + text += chr; + return this; + } + } + } + + return null; + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + if (down) { + if (isMouseOver(pos)) { + return this; + } + } + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + boolean hover = isMouseOver(mouse) || focused; + + RGB cBdr = getColor(BDR, mouse, hover); + RGB cBg = getColor(BG, mouse, hover); + RGB cFg = getColor(FG, mouse, hover); + + RenderUtils.quadBorder(rect, borderWidth, cBdr, cBg); + + boolean cursor = focused && enabled && System.currentTimeMillis() % 800 < 400; + + Coord txtPos = rect.getMin().add(paddingX + borderWidth, paddingY + borderWidth - 2).round_ip(); + String shown = getShownText(text); + font.draw(txtPos, shown + (cursor ? "|" : ""), cFg, Align.LEFT); + } + + + /** + * Set chars allowed in this text input. + * + * @param allowed allowed chars + * @return this + */ + public TextInput setAllowedChars(String allowed) + { + this.allowedChars = allowed; + return this; + } + + + public TextInput setBorderSize(double width) + { + borderWidth = width; + return this; + } + + + public TextInput setPadding(double x, double y) + { + paddingX = x; + paddingY = y; + return this; + } + + + public TextInput setPasswordMode(boolean state) + { + passwordMode = state; + return this; + } + +} diff --git a/src/net/tortuga/gui/widgets/layout/AmbRenderer.java b/src/net/tortuga/gui/widgets/layout/AmbRenderer.java new file mode 100644 index 0000000..26df0e9 --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/AmbRenderer.java @@ -0,0 +1,57 @@ +package net.tortuga.gui.widgets.layout; + + +import net.tortuga.App; +import net.tortuga.gui.widgets.Widget; + +import com.porcupine.coord.Coord; + + +/** + * Space + * + * @author MightyPork + */ +public class AmbRenderer extends Widget { + + public AmbRenderer() { + setMargins(0, 0, 0, 0); + setMinSize(0, 0); + } + + + @Override + public void render(Coord mouse) + { + App.weatherAnimation.render(App.currentDelta); + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + rect.setTo(0, 0, 0, 0); + } + +} diff --git a/src/net/tortuga/gui/widgets/layout/FullWidthLayout.java b/src/net/tortuga/gui/widgets/layout/FullWidthLayout.java new file mode 100644 index 0000000..673d0bf --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/FullWidthLayout.java @@ -0,0 +1,199 @@ +package net.tortuga.gui.widgets.layout; + + +import static org.lwjgl.opengl.GL11.*; +import net.tortuga.App; +import net.tortuga.gui.widgets.GuiRoot; +import net.tortuga.gui.widgets.IRefreshable; +import net.tortuga.gui.widgets.IWidget; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.util.Align; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Vec; + + +/** + * Frame for widgets (with shadow and border + background) + * + * @author MightyPork + */ +public class FullWidthLayout extends Widget implements IRefreshable { + + public int alignH = Align.CENTER; + public int alignV = Align.CENTER; + + private Widget child = new Gap(0, 0); + + + public FullWidthLayout(Widget child) { + this(); + add(child); + } + + + public FullWidthLayout(Widget child, int alignH, int alignV) { + this(); + add(child); + this.alignH = alignH; + this.alignV = alignV; + } + + + public FullWidthLayout(int alignH, int alignV) { + this(); + this.alignH = alignH; + this.alignV = alignV; + } + + + public FullWidthLayout() { + setMargins(0, 0, 0, 0); + } + + + /** + * Set the primary and only child. + */ + @Override + public void add(Widget child) + { + this.child = child; + setMinWidth(App.inst.getSize().xi()); + child.setMinWidth(App.inst.getSize().xi() - child.getMargins().getHorizontal()); + } + + + @Override + public void calcChildSizes() + { + child.calcChildSizes(); + + rect.setTo(0, 0, minSize.x, child.getSize().y); + + //Coord lbPos = rect.getCenter(); + Coord childMove = new Coord(); + Coord sizeC = child.getSize(); + + switch (alignH) { + case Align.LEFT: + childMove.x = 0; + break; + case Align.RIGHT: + childMove.x = rect.getSize().x - sizeC.x; + break; + case Align.CENTER: + childMove.x = rect.getSize().x / 2 - sizeC.x / 2; + break; + } + +// switch (alignV) { +// case Align.TOP: +// childMove.y = rect.getSize().y - sizeC.y; +// break; +// case Align.BOTTOM: +// childMove.y = 0; +// break; +// case Align.CENTER: +// childMove.y = rect.getSize().y / 2 - sizeC.y / 2; +// break; +// } + + child.rect.add_ip(new Vec(childMove)); + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + if (!isVisible() || !isEnabled()) return null; + + if (!child.isEnabled() || !child.isVisible()) return null; + return child.onKey(key, chr, down); + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + if (!isVisible() || !isEnabled()) return null; + + Coord pos_r = pos.sub(rect.getMin()); + + if (!child.isEnabled() || !child.isVisible()) return null; + return child.onMouseButton(pos_r, button, down); + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + if (!isVisible() || !isEnabled()) return null; + + Coord pos_r = pos.sub(rect.getMin()); + if (!child.isEnabled() || !child.isVisible()) return null; + return child.onScroll(pos_r, scroll); + } + + + @Override + public void handleStaticInputs(Coord pos) + { + if (!isVisible() || !isEnabled()) return; + + Coord pos_r = pos.sub(rect.getMin()); + + if (!child.isEnabled() || !child.isVisible()) return; + child.handleStaticInputs(pos_r); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + glPushMatrix(); + glPushAttrib(GL_ENABLE_BIT); + + glTranslated(rect.x1(), rect.y1(), 2); + child.render(mouse.sub(rect.getMin())); + + glPopAttrib(); + glPopMatrix(); + } + + + public FullWidthLayout setAlignH(int align) + { + alignH = align; + return this; + } + + + public FullWidthLayout setAlignV(int align) + { + alignV = align; + return this; + } + + + @Override + public IWidget setGuiRoot(GuiRoot guiContainer) + { + super.setGuiRoot(guiContainer); + child.setGuiRoot(guiContainer); + + guiContainer.scheduleRefresh(this); + return this; + } + + + @Override + public void refresh() + { + setMinWidth(App.inst.getSize().xi()); + child.setMinWidth(App.inst.getSize().xi() - child.getMargins().getHorizontal()); + calcChildSizes(); + } +} diff --git a/src/net/tortuga/gui/widgets/layout/Gap.java b/src/net/tortuga/gui/widgets/layout/Gap.java new file mode 100644 index 0000000..070463f --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/Gap.java @@ -0,0 +1,60 @@ +package net.tortuga.gui.widgets.layout; + + +import net.tortuga.gui.widgets.Widget; + +import com.porcupine.coord.Coord; + + +/** + * Space + * + * @author MightyPork + */ +public class Gap extends Widget { + + /** + * Gui space (gap) + * + * @param width min width + * @param height min height + */ + public Gap(int width, int height) { + setMargins(0, 0, 0, 0); + setMinSize(width, height); + } + + + @Override + public void render(Coord mouse) + {} + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + rect.setTo(0, 0, minSize.x, minSize.y); + } + +} diff --git a/src/net/tortuga/gui/widgets/layout/LayoutBase.java b/src/net/tortuga/gui/widgets/layout/LayoutBase.java new file mode 100644 index 0000000..d8782f4 --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/LayoutBase.java @@ -0,0 +1,141 @@ +package net.tortuga.gui.widgets.layout; + + +import static org.lwjgl.opengl.GL11.*; + +import java.util.ArrayList; +import java.util.Collections; + +import net.tortuga.gui.widgets.GuiRoot; +import net.tortuga.gui.widgets.IWidget; +import net.tortuga.gui.widgets.Widget; + +import com.porcupine.coord.Coord; + + +/** + * Base for layouts. + * + * @author MightyPork + */ +public abstract class LayoutBase extends Widget { + + /** layout children. */ + public ArrayList children = new ArrayList(); + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + ArrayList renderlist = new ArrayList(children); + Collections.sort(renderlist); + + glPushMatrix(); + glTranslated(rect.x1(), rect.y1(), 4); + for (Widget child : renderlist) { + glPushMatrix(); + glPushAttrib(GL_ENABLE_BIT); + glTranslated(0, 0, 0.1 * child.renderIndex); + Coord pos_r = mouse.sub(rect.getMin()); + child.render(pos_r); + glPopAttrib(); + glPopMatrix(); + } + glPopMatrix(); + + //RenderUtils.quadBorder(rect, 1, RGB.PURPLE, null); + + } + + + @Override + public void handleStaticInputs(Coord pos) + { + if (!isVisible() || !isEnabled()) return; + + Coord pos_r = pos.sub(rect.getMin()); + for (Widget child : children) { + if (!child.isEnabled() || !child.isVisible()) continue; + child.handleStaticInputs(pos_r); + } + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + if (!isVisible() || !isEnabled()) return null; + + Coord pos_r = pos.sub(rect.getMin()); + for (Widget child : children) { + if (!child.isEnabled() || !child.isVisible()) continue; + Widget consumer = child.onMouseButton(pos_r, button, down); + if (consumer != null) return consumer; + } + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + if (!isVisible() || !isEnabled()) return null; + + Coord pos_r = pos.sub(rect.getMin()); + for (Widget child : children) { + if (!child.isEnabled() || !child.isVisible()) continue; + Widget consumer = child.onScroll(pos_r, scroll); + if (consumer != null) return consumer; + } + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + if (!isVisible() || !isEnabled()) return null; + + for (Widget child : children) { + if (!child.isEnabled() || !child.isVisible()) continue; + Widget consumer = child.onKey(key, chr, down); + if (consumer != null) return consumer; + } + return null; + } + + + @Override + public abstract void calcChildSizes(); + + + @Override + public void add(Widget child) + { + children.add(child); + } + + + @Override + public void removeAll() + { + children.clear(); + } + + + @Override + public IWidget setGuiRoot(GuiRoot guiContainer) + { + super.setGuiRoot(guiContainer); + for (Widget child : children) { + child.setGuiRoot(guiContainer); + } + + calcChildSizes(); + + return this; + } + +} diff --git a/src/net/tortuga/gui/widgets/layout/LayoutH.java b/src/net/tortuga/gui/widgets/layout/LayoutH.java new file mode 100644 index 0000000..d8cb4cc --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/LayoutH.java @@ -0,0 +1,144 @@ +package net.tortuga.gui.widgets.layout; + + +import static net.tortuga.util.Align.*; +import net.tortuga.gui.widgets.Widget; + +import com.porcupine.coord.Vec; +import com.porcupine.math.Calc; + + +/** + * Horizontal layout widget + * + * @author MightyPork + */ +public class LayoutH extends LayoutBase { + + /** vertical align */ + private int alignV = CENTER; + /** horizontal align */ + private int alignH = CENTER; + + + /** + * New horizontal layout widget + * + * @param alignH horizontal align + * @param alignV vertical align. -1 bottom, 0 center, 1 top. + */ + public LayoutH(int alignH, int alignV) { + this(); + setAlign(alignH, alignV); + } + + + /** + * new Vertical layout widget (center, center) + */ + public LayoutH() { + setMargins(0, 0, 0, 0); + } + + + /** + * Set align (used if minSize is larger than needed for contents) + * + * @param alignH horizontal align + * @param alignV vertical align + * @return this + */ + public LayoutH setAlign(int alignH, int alignV) + { + this.alignV = alignV; + this.alignH = alignH; + return this; + } + + + @Override + public void calcChildSizes() + { + double lastMargin = 0; + double totalSize = 0; + double maxVerticalSize = 0; + + // measure max width for alignment. + for (Widget child : children) { + child.calcChildSizes(); + maxVerticalSize = Math.max(maxVerticalSize, child.getSize().y + child.getMargins().top + child.getMargins().bottom); + } + + boolean first = true; + // generate rects + for (Widget child : children) { + // add whats required by margins. + if (!first && child.getSize().x > 0) { + totalSize += Calc.max(lastMargin, child.getMargins().left); + } + + first = false; + switch (alignV) { + case BOTTOM: + child.rect.add_ip(new Vec(totalSize, child.getMargins().bottom)); + break; + case CENTER: + child.rect.add_ip(new Vec(totalSize, (maxVerticalSize - child.getSize().y) / 2)); + break; + case TOP: + child.rect.add_ip(new Vec(totalSize, (maxVerticalSize - child.getSize().y - child.getMargins().top))); + } + + totalSize += child.getSize().x; + lastMargin = child.getMargins().right; + } + + if (Math.round(totalSize) % 2 == 1) { + totalSize += 1; + } + + if (Math.round(maxVerticalSize) % 2 == 1) { + maxVerticalSize += 1; + } + + this.rect.setTo(0, 0, Math.max(minSize.x, totalSize), Math.max(minSize.y, maxVerticalSize)); + + if (minSize.y > maxVerticalSize) { + switch (alignV) { + case TOP: + for (Widget child : children) { + child.getRect().add_ip(0, minSize.y - maxVerticalSize); + } + break; + + case CENTER: + for (Widget child : children) { + child.getRect().add_ip(0, (minSize.y - maxVerticalSize) / 2); + } + break; + + case BOTTOM: + break; + } + } + + if (minSize.x > totalSize) { + switch (alignH) { + case RIGHT: + for (Widget child : children) { + child.getRect().add_ip(minSize.x - totalSize, 0); + } + break; + + case CENTER: + for (Widget child : children) { + child.getRect().add_ip((minSize.x - totalSize) / 2, 0); + } + break; + + case LEFT: + break; + } + } + } +} diff --git a/src/net/tortuga/gui/widgets/layout/LayoutV.java b/src/net/tortuga/gui/widgets/layout/LayoutV.java new file mode 100644 index 0000000..3fdeabb --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/LayoutV.java @@ -0,0 +1,146 @@ +package net.tortuga.gui.widgets.layout; + + +import static net.tortuga.util.Align.*; +import net.tortuga.gui.widgets.Widget; + +import com.porcupine.coord.Vec; +import com.porcupine.math.Calc; + + +/** + * Vertical layout widget + * + * @author MightyPork + */ +public class LayoutV extends LayoutBase { + + /** horizontal align */ + private int alignH = CENTER; + private int alignV = CENTER; + + + /** + * new Vertical layout widget + * + * @param alignH horizontal align + * @param alignV vertical align + */ + public LayoutV(int alignH, int alignV) { + this(); + setAlign(alignH, alignV); + } + + + /** + * new Vertical layout widget (center, center) + */ + public LayoutV() { + setMargins(0, 0, 0, 0); + } + + + /** + * Set align (used if minSize is larger than needed for contents) + * + * @param alignH horizontal align + * @param alignV vertical align + * @return this + */ + public LayoutV setAlign(int alignH, int alignV) + { + this.alignV = alignV; + this.alignH = alignH; + return this; + } + + + @Override + public void calcChildSizes() + { + double lastMargin = 0; + double totalSize = 0; + double maxHorizontalSize = 0; + + // measure max width for alignment. + for (Widget child : children) { + child.calcChildSizes(); + maxHorizontalSize = Math.max(maxHorizontalSize, child.getSize().x + child.getMargins().left + child.getMargins().right); + } + + maxHorizontalSize = Math.max(maxHorizontalSize, minSize.x); + + // generate rects + boolean first = true; + for (int i = children.size() - 1; i >= 0; i--) { + Widget child = children.get(i); + // add whats required by margins. + if (!first) { + totalSize += Calc.max(lastMargin, child.getMargins().bottom); + } + first = false; + switch (alignH) { + case LEFT: + child.rect.add_ip(new Vec(child.getMargins().left, totalSize)); + break; + case CENTER: + child.rect.add_ip(new Vec((maxHorizontalSize - child.getSize().x) / 2, totalSize)); + break; + case RIGHT: + child.rect.add_ip(new Vec((maxHorizontalSize - child.getSize().x - child.getMargins().right), totalSize)); + } + + totalSize += child.getSize().y; + lastMargin = child.getMargins().top; + } + + if (Math.round(totalSize) % 2 == 1) { + totalSize += 1; + } + + if (Math.round(maxHorizontalSize) % 2 == 1) { + maxHorizontalSize += 1; + } + + this.rect.setTo(0, 0, (int) Math.round(Math.max(minSize.x, maxHorizontalSize)), (int) Math.round(Math.max(minSize.y, totalSize))); + + if (minSize.y > totalSize) { + switch (alignV) { + case TOP: + for (Widget child : children) { + child.getRect().add_ip(0, minSize.y - totalSize); + } + break; + + case CENTER: + for (Widget child : children) { + child.getRect().add_ip(0, (minSize.y - totalSize) / 2); + } + break; + + case BOTTOM: + break; + } + } + + if (minSize.x > maxHorizontalSize) { + switch (alignH) { + case RIGHT: + for (Widget child : children) { + child.getRect().add_ip(minSize.x - maxHorizontalSize, 0); + } + break; + + case CENTER: + for (Widget child : children) { + child.getRect().add_ip((minSize.x - maxHorizontalSize) / 2, 0); + } + break; + + case LEFT: + break; + } + } + } + +} diff --git a/src/net/tortuga/gui/widgets/layout/ScrollingLayoutH.java b/src/net/tortuga/gui/widgets/layout/ScrollingLayoutH.java new file mode 100644 index 0000000..7fb0304 --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/ScrollingLayoutH.java @@ -0,0 +1,235 @@ +package net.tortuga.gui.widgets.layout; + + +import java.util.ArrayList; + +import net.tortuga.gui.widgets.IScrollable; +import net.tortuga.gui.widgets.IScrollbar; +import net.tortuga.gui.widgets.IWidgetFactory; +import net.tortuga.gui.widgets.LeftTopRightBottom; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.util.Log; + +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Horizontal scrolling layout + * + * @author MightyPork + */ +public class ScrollingLayoutH extends LayoutH implements IScrollable { + + public boolean delegatedScrollEnabled = true; + + private IScrollbar scrollbar; + + private ArrayList positions; + + private IWidgetFactory factory; + + + /** + * Create scrolling layout V.
+ * After creating, this layout must be filled with at least the minimal + * amount of children to stretch it accordingly.
+ * It does not work well with children of different sizes. + * + * @param childrenShown number of shown children + * @param blankChildFactory chidlren factory + */ + public ScrollingLayoutH(int childrenShown, IWidgetFactory blankChildFactory) { + super(); + this.childrenShown = childrenShown; + + for (int i = 1; i <= childrenShown; i++) + addChild(blankChildFactory.getWidget()); + rememberPositions(); + removeAll(); + factory = blankChildFactory; + } + + + public void adaptForSize(int childrenCount) + { + ArrayList childrenBackup = new ArrayList(allChildren); + removeAll(); + setMinSize(0, 0); + + this.childrenShown = childrenCount; + + for (int i = 1; i <= childrenShown; i++) { + addChild(factory.getWidget()); + } + + rememberPositions(); + removeAll(); + + for (Widget w : childrenBackup) { + addChild(w); + } + } + + private class ChildPos { + + public ChildPos(Widget w) { + this.childRect = w.rect.copy(); + this.childMargins = w.margins.copy(); + this.childMinSize = w.minSize.copy(); + } + + + public void modify(Widget w) + { + w.rect.setTo(this.childRect); + w.margins.setTo(this.childMargins); + w.minSize.setTo(this.childMinSize); + } + + public Rect childRect; + public LeftTopRightBottom childMargins; + public Coord childMinSize; + } + + + private void rememberPositions() + { + calcChildSizes(); + positions = new ArrayList(childrenShown); + for (Widget ch : children) { + if (ch == null) { + Log.w("Null child in ScrollingLayoutH!"); + } else { + positions.add(new ChildPos(ch)); + } + } + + setMinSize(getSize()); + } + + private int childrenShown = 1; + + private ArrayList allChildren = new ArrayList(); + + + /** + * Add child to group + * + * @param widget + */ + public void addChild(Widget widget) + { + allChildren.add(widget); + if (children.size() < childrenShown) { + children.add(widget); + } else { + widget.setVisible(false); + } + widget.setGuiRoot(guiRoot); + } + + + @Override + @Deprecated + public void add(Widget child) + {} + + + @Override + public double getContentSize() + { + return allChildren.size(); + } + + + @Override + public double getViewSize() + { + return childrenShown; + } + + + @Override + public void onScrollbarChange(double value) + { + int startEntry = (int) ((getContentSize() - getViewSize()) * value); + + for (Widget w : children) { + w.setVisible(false); + } + + children.clear(); + if (allChildren.size() < childrenShown) { + for (int i = 0, c = 0; i < allChildren.size(); i++, c++) { + try { + Widget w; + children.add(w = allChildren.get(i)); + w.setGuiRoot(getGuiRoot()); + w.setVisible(true); + positions.get(c).modify(w); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + } else { + for (int i = startEntry, c = 0; i < startEntry + childrenShown; i++, c++) { + try { + Widget w; + children.add(w = allChildren.get(i)); + w.setGuiRoot(getGuiRoot()); + w.setVisible(true); + positions.get(c).modify(w); + + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + //calcChildSizes(); + } + + + @Override + public void removeAll() + { + children.clear(); + allChildren.clear(); + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + // event keys that may be used by widgets inside + if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)) { + return null; + } + + if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)) { + return null; + } + + if (Mouse.isButtonDown(1)) { + return null; + } + + if (!delegatedScrollEnabled) return null; + if (!isMouseOver(pos)) return null; + return scrollbar.onScrollDelegate(scroll); + } + + + @Override + public void onScrollbarConnected(IScrollbar scrollbar) + { + this.scrollbar = scrollbar; + } + +} diff --git a/src/net/tortuga/gui/widgets/layout/ScrollingLayoutV.java b/src/net/tortuga/gui/widgets/layout/ScrollingLayoutV.java new file mode 100644 index 0000000..a95ad50 --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/ScrollingLayoutV.java @@ -0,0 +1,218 @@ +package net.tortuga.gui.widgets.layout; + + +import java.util.ArrayList; + +import net.tortuga.gui.widgets.IScrollable; +import net.tortuga.gui.widgets.IScrollbar; +import net.tortuga.gui.widgets.IWidgetFactory; +import net.tortuga.gui.widgets.LeftTopRightBottom; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.util.Log; + +import org.lwjgl.input.Keyboard; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +public class ScrollingLayoutV extends LayoutV implements IScrollable { + + public boolean delegatedScrollEnabled = true; + + private IScrollbar scrollbar; + + private ArrayList positions; + + private IWidgetFactory factory; + + + /** + * Create scrolling layout V.
+ * After creating, this layout must be filled with at least the minimal + * amount of children to stretch it accordingly.
+ * It does not work well with children of different sizes. + * + * @param childrenShown number of shown children + * @param blankChildFactory children factory + */ + public ScrollingLayoutV(int childrenShown, IWidgetFactory blankChildFactory) { + super(); + this.childrenShown = childrenShown; + + for (int i = 1; i <= childrenShown; i++) + addChild(blankChildFactory.getWidget()); + rememberPositions(); + removeAll(); + factory = blankChildFactory; + } + + private class ChildPos { + + public ChildPos(Widget w) { + this.rect = w.rect.copy(); + this.margins = w.margins.copy(); + this.minSize = w.minSize.copy(); + } + + + public void modify(Widget w) + { + w.rect.setTo(this.rect); + w.margins.setTo(this.margins); + w.minSize.setTo(this.minSize); + } + + public Rect rect; + public LeftTopRightBottom margins; + public Coord minSize; + } + + + public void adaptForSize(int childrenCount) + { + ArrayList childrenBackup = new ArrayList(allChildren); + removeAll(); + setMinSize(0, 0); + + this.childrenShown = childrenCount; + + for (int i = 1; i <= childrenShown; i++) { + addChild(factory.getWidget()); + } + + rememberPositions(); + removeAll(); + + for (Widget w : childrenBackup) { + addChild(w); + } + } + + + private void rememberPositions() + { + calcChildSizes(); + positions = new ArrayList(childrenShown); + for (Widget ch : children) { + if (ch == null) { + Log.w("Null child in ScrollingLayoutV!"); + } else { + positions.add(new ChildPos(ch)); + } + } + + //Collections.reverse(positions); + setMinSize(getSize()); + } + + private int childrenShown = 1; + + private ArrayList allChildren = new ArrayList(); + + + public void addChild(Widget widget) + { + allChildren.add(widget); + if (children.size() < childrenShown) { + children.add(widget); + } else { + widget.setVisible(false); + } + } + + + @Override + @Deprecated + public void add(Widget child) + {} + + + @Override + public double getContentSize() + { + return allChildren.size(); + } + + + @Override + public double getViewSize() + { + return childrenShown; + } + + + @Override + public void onScrollbarChange(double value) + { + int startEntry = (int) ((getContentSize() - getViewSize()) * value); + + for (Widget w : children) { + w.setVisible(false); + } + + children.clear(); + if (allChildren.size() < childrenShown) { + for (int i = 0, c = 0; i < allChildren.size(); i++, c++) { + try { + Widget w; + children.add(w = allChildren.get(i)); + w.setGuiRoot(getGuiRoot()); + w.setVisible(true); + positions.get(c).modify(w); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + } else { + for (int i = startEntry, c = 0; i < startEntry + childrenShown; i++, c++) { + try { + Widget w; + children.add(w = allChildren.get(i)); + w.setGuiRoot(getGuiRoot()); + w.setVisible(true); + positions.get(c).modify(w); + + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + //calcChildSizes(); + } + + + @Override + public void removeAll() + { + children.clear(); + allChildren.clear(); + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)) { + return null; + } + + if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)) { + return null; + } + if (!delegatedScrollEnabled) return null; + if (!isMouseOver(pos)) return null; + return scrollbar.onScrollDelegate(scroll); + } + + + @Override + public void onScrollbarConnected(IScrollbar scrollbar) + { + this.scrollbar = scrollbar; + } + +} diff --git a/src/net/tortuga/gui/widgets/layout/frame/FrameBase.java b/src/net/tortuga/gui/widgets/layout/frame/FrameBase.java new file mode 100644 index 0000000..6c5aa8c --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/frame/FrameBase.java @@ -0,0 +1,252 @@ +package net.tortuga.gui.widgets.layout.frame; + + +import static org.lwjgl.opengl.GL11.*; +import net.tortuga.gui.widgets.GuiRoot; +import net.tortuga.gui.widgets.IWidget; +import net.tortuga.gui.widgets.LeftTopRightBottom; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.gui.widgets.layout.Gap; +import net.tortuga.util.Align; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Vec; + + +/** + * Frame for widgets (with shadow and border + background) + * + * @author MightyPork + */ +public abstract class FrameBase extends Widget { + + /** Horizontal align */ + public int alignH = Align.CENTER; + /** Vertical align */ + public int alignV = Align.CENTER; + + /** Border width */ + public double borderWidth = 3; + + /** Frame child */ + public Widget child = new Gap(0, 0); + + /** Padding */ + public LeftTopRightBottom padding = new LeftTopRightBottom(5, 5, 5, 5); + + + /** + * Set the primary and only child. + */ + @Override + public final void add(Widget child) + { + this.child = child; + } + + + @Override + public final void calcChildSizes() + { + child.setGuiRoot(guiRoot); + + child.calcChildSizes(); + + //@formatter:off + setMinSize( (int)(Math.max(minSize.x, child.getSize().x + padding.getHorizontal() + borderWidth * 2)), + (int)(Math.max(minSize.y, child.getSize().y + padding.getVetical() + borderWidth * 2))); + //@formatter:on + + rect.setTo(0, 0, minSize.x, minSize.y); + + //Coord lbPos = rect.getCenter(); + Coord childMove = new Coord(); + Coord sizeC = child.getSize(); + + double bdrL = padding.left + borderWidth; + double bdrT = padding.top + borderWidth; + double bdrR = padding.right + borderWidth; + double bdrB = padding.bottom + borderWidth; + + switch (alignH) { + case Align.LEFT: + childMove.x = bdrL; + break; + case Align.RIGHT: + childMove.x = rect.getSize().x - bdrR - sizeC.x; + break; + case Align.CENTER: + childMove.x = rect.getSize().x / 2 - sizeC.x / 2; + break; + } + + switch (alignV) { + case Align.TOP: + childMove.y = rect.getSize().y - bdrT - sizeC.y; + break; + case Align.BOTTOM: + childMove.y = bdrB; + break; + case Align.CENTER: + childMove.y = rect.getSize().y / 2 - sizeC.y / 2; + break; + } + + child.rect.add_ip(new Vec(childMove)); + } + + + @Override + public final Widget onKey(int key, char chr, boolean down) + { + if (!isVisible() || !isEnabled()) return null; + + if (!child.isEnabled() || !child.isVisible()) return null; + return child.onKey(key, chr, down); + } + + + @Override + public final Widget onMouseButton(Coord pos, int button, boolean down) + { + if (!isVisible() || !isEnabled()) return null; + + Coord pos_r = pos.sub(rect.getMin()); + + if (!child.isEnabled() || !child.isVisible()) return null; + return child.onMouseButton(pos_r, button, down); + } + + + @Override + public final Widget onScroll(Coord pos, int scroll) + { + if (!isVisible() || !isEnabled()) return null; + + Coord pos_r = pos.sub(rect.getMin()); + if (!child.isEnabled() || !child.isVisible()) return null; + return child.onScroll(pos_r, scroll); + } + + + @Override + public final void handleStaticInputs(Coord pos) + { + if (!isVisible() || !isEnabled()) return; + + Coord pos_r = pos.sub(rect.getMin()); + + if (!child.isEnabled() || !child.isVisible()) return; + child.handleStaticInputs(pos_r); + } + + + @Override + public final void render(Coord mouse) + { + if (!isVisible()) return; + + renderFrame(mouse); + + // CHILD + glPushMatrix(); + glPushAttrib(GL_ENABLE_BIT); + + glTranslated(rect.x1(), rect.y1(), 2); + child.render(mouse.sub(rect.getMin())); + + glPopAttrib(); + glPopMatrix(); + + //RenderUtils.quadBorder(rect.grow(1, 1), 1, RGB.YELLOW, null); + } + + + /** + * Render frame decorations (not child) + * + * @param mouse + */ + protected abstract void renderFrame(Coord mouse); + + + /** + * Set horizontal align + * + * @param align align + * @return this + */ + public final FrameBase setAlignH(int align) + { + alignH = align; + return this; + } + + + /** + * Set vertical align + * + * @param align align + * @return this + */ + public final FrameBase setAlignV(int align) + { + alignV = align; + return this; + } + + + @Override + public final IWidget setGuiRoot(GuiRoot guiContainer) + { + super.setGuiRoot(guiContainer); + child.setGuiRoot(guiContainer); + return this; + } + + + /** + * Set padding (inside distance from border) + * + * @param left left padding + * @param right right padding + * @param top top padding + * @param bottom bottom padding + * @return this + */ + public final FrameBase setPadding(int left, int right, int top, int bottom) + { + padding.setTo(left, top, right, bottom); + return this; + } + + + /** + * Set horizontal padding (inside distance from border) + * + * @param left left padding + * @param right right padding + * @return this + */ + public final FrameBase setPaddingH(int left, int right) + { + padding.left = left; + padding.right = right; + return this; + } + + + /** + * Set vertical padding (inside distance from border) + * + * @param top top padding + * @param bottom bottom padding + * @return this + */ + public final FrameBase setPaddingV(int top, int bottom) + { + padding.top = top; + padding.bottom = bottom; + return this; + } +} diff --git a/src/net/tortuga/gui/widgets/layout/frame/FrameBelt.java b/src/net/tortuga/gui/widgets/layout/frame/FrameBelt.java new file mode 100644 index 0000000..a552815 --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/frame/FrameBelt.java @@ -0,0 +1,54 @@ +package net.tortuga.gui.widgets.layout.frame; + + +import net.tortuga.gui.widgets.Widget; +import net.tortuga.textures.Textures; +import net.tortuga.util.RenderUtils; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Frame for widgets (with shadow and border + background) + * + * @author MightyPork + */ +public class FrameBelt extends FrameBase { + + public FrameBelt() { + setMarginsH(0, 0); + setPaddingV(36 + 5, 36 + 5); + setMarginsV(0, 0); + } + + + public FrameBelt(Widget child) { + this(); + add(child); + } + + + public FrameBelt(Widget child, int alignH, int alignV) { + this(child); + this.alignH = alignH; + this.alignV = alignV; + } + + + @Override + public void renderFrame(Coord mouse) + { + // BACKGROUND + Rect r = rect.grow(0, -36); + RenderUtils.quadTextured(r, new Rect(0, 0, r.getSize().x, r.getSize().y), Textures.steel_brushed); + + Rect belt1 = rect.getEdgeTop().growDown_ip(50).add_ip(0, 11); + Rect belt1tx = new Rect(0, 0, belt1.getSize().x, 50); + RenderUtils.quadTextured(belt1, belt1tx, Textures.steel_rivet_belts); + + Rect belt2 = rect.getEdgeBottom().growUp_ip(50).sub_ip(0, 11); + Rect belt2t = new Rect(0, 256 - 50, (int) rect.getSize().x, 256); + RenderUtils.quadTextured(belt2, belt2t, Textures.steel_rivet_belts); + } +} diff --git a/src/net/tortuga/gui/widgets/layout/frame/FrameBottom.java b/src/net/tortuga/gui/widgets/layout/frame/FrameBottom.java new file mode 100644 index 0000000..7c3226e --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/frame/FrameBottom.java @@ -0,0 +1,98 @@ +package net.tortuga.gui.widgets.layout.frame; + + +import net.tortuga.gui.widgets.Widget; +import net.tortuga.textures.Textures; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Frame for widgets (with shadow and border + background) + * + * @author MightyPork + */ +public class FrameBottom extends FrameBase { + + public double shadowY = 5; + + public boolean showBorder = true; + public boolean showShadow = true; + + + public FrameBottom() { + setMarginsH(0, 0); + } + + + public FrameBottom(Widget child) { + this(); + add(child); + } + + + public FrameBottom(Widget child, int alignH, int alignV) { + this(child); + this.alignH = alignH; + this.alignV = alignV; + } + + + public FrameBottom enableBorder(boolean flag) + { + showBorder = flag; + if (!flag) borderWidth = 0; + return this; + } + + + public FrameBottom enableShadow(boolean flag) + { + showShadow = flag; + return this; + } + + + public FrameBottom setBorderSize(double width) + { + borderWidth = width; + showBorder = true; + return this; + } + + + public FrameBottom setShadowOffset(double shiftY) + { + shadowY = shiftY; + showShadow = true; + return this; + } + + + @Override + public void renderFrame(Coord mouse) + { + // SHADOW + if (showShadow) { + Rect below = rect.getEdgeTop().growUp_ip(shadowY); + + RenderUtils.quadGradV(below, RGB.TRANSPARENT, new RGB(0, 0.3)); + } + + // BACKGROUND + RenderUtils.quadTextured(rect, rect, Textures.steel_big_scratched, new RGB(0xd29f8e));//0xb4745e + + if (showBorder) { + Rect line = rect.getEdgeTop(); + + line.growDown_ip(2); + RenderUtils.quadRect(line, new RGB(0, 0.2)); + + line.growDown_ip(2); + RenderUtils.quadRect(line, new RGB(0, 0.2)); + } + } +} diff --git a/src/net/tortuga/gui/widgets/layout/frame/FrameTop.java b/src/net/tortuga/gui/widgets/layout/frame/FrameTop.java new file mode 100644 index 0000000..ebbcb4a --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/frame/FrameTop.java @@ -0,0 +1,98 @@ +package net.tortuga.gui.widgets.layout.frame; + + +import net.tortuga.gui.widgets.Widget; +import net.tortuga.textures.Textures; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Frame for widgets (with shadow and border + background) + * + * @author MightyPork + */ +public class FrameTop extends FrameBase { + + public double shadowY = 5; + + public boolean showBorder = true; + public boolean showShadow = true; + + + public FrameTop() { + setMarginsH(0, 0); + } + + + public FrameTop(Widget child) { + this(); + add(child); + } + + + public FrameTop(Widget child, int alignH, int alignV) { + this(child); + this.alignH = alignH; + this.alignV = alignV; + } + + + public FrameTop enableBorder(boolean flag) + { + showBorder = flag; + if (!flag) borderWidth = 0; + return this; + } + + + public FrameTop enableShadow(boolean flag) + { + showShadow = flag; + return this; + } + + + public FrameTop setBorderSize(double width) + { + borderWidth = width; + showBorder = true; + return this; + } + + + public FrameTop setShadowOffset(double shiftY) + { + shadowY = shiftY; + showShadow = true; + return this; + } + + + @Override + public void renderFrame(Coord mouse) + { + // SHADOW + if (showShadow) { + Rect below = rect.getEdgeBottom().growDown_ip(shadowY); + + RenderUtils.quadGradV(below, new RGB(0, 0.3), RGB.TRANSPARENT); + } + + // BACKGROUND + RenderUtils.quadTextured(rect, rect, Textures.steel_big_scratched, new RGB(0xd29f8e));//0xb4745e + + if (showBorder) { + Rect line = rect.getEdgeBottom(); + + line.growUp_ip(2); + RenderUtils.quadRect(line, new RGB(0, 0.2)); + + line.growUp_ip(2); + RenderUtils.quadRect(line, new RGB(0, 0.2)); + } + } +} diff --git a/src/net/tortuga/gui/widgets/layout/frame/FrameWindow.java b/src/net/tortuga/gui/widgets/layout/frame/FrameWindow.java new file mode 100644 index 0000000..2d7061d --- /dev/null +++ b/src/net/tortuga/gui/widgets/layout/frame/FrameWindow.java @@ -0,0 +1,150 @@ +package net.tortuga.gui.widgets.layout.frame; + + +import static org.lwjgl.opengl.GL11.*; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.textures.Textures; +import net.tortuga.textures.Tx; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Frame for widgets (with shadow and border + background) + * + * @author MightyPork + */ +public class FrameWindow extends FrameBase { + + public boolean showScrews = true; + + public double shadowX = 10; + public double shadowY = 10; + + public boolean showBorder = true; + public boolean showShadow = true; + + private int[] screws = new int[4]; + + + public FrameWindow(Widget child) { + this(); + add(child); + } + + + public FrameWindow(Widget child, int alignH, int alignV) { + this(); + add(child); + this.alignH = alignH; + this.alignV = alignV; + } + + + public FrameWindow(int alignH, int alignV) { + this(); + this.alignH = alignH; + this.alignV = alignV; + } + + + public FrameWindow() { + screws[0] = rand.nextInt(360); + screws[1] = rand.nextInt(360); + screws[2] = rand.nextInt(360); + screws[3] = rand.nextInt(360); + } + + + public FrameWindow enableBorder(boolean flag) + { + showBorder = flag; + if (!flag) borderWidth = 0; + return this; + } + + + public FrameWindow enableShadow(boolean flag) + { + showShadow = flag; + return this; + } + + + public FrameWindow enableScrews(boolean flag) + { + showScrews = flag; + return this; + } + + + public FrameWindow setBorderSize(double width) + { + borderWidth = width; + showBorder = true; + return this; + } + + + public FrameWindow setShadowOffset(double shiftX, double shiftY) + { + shadowX = shiftX; + shadowY = shiftY; + showShadow = true; + return this; + } + + + @Override + public void renderFrame(Coord mouse) + { + // SHADOW + if (showShadow) { + RenderUtils.quadRect(rect.add(shadowX, -shadowY).grow(-1, -1), new RGB(0, 0.1)); + RenderUtils.quadRect(rect.add(shadowX, -shadowY).grow(0, 0), new RGB(0, 0.1)); + RenderUtils.quadRect(rect.add(shadowX, -shadowY).grow(1, 1), new RGB(0, 0.1)); + } + + // BACKGROUND + RenderUtils.quadTextured(rect, rect, Textures.steel_big_lt); + + if (showBorder) { + RenderUtils.quadBorder(rect, 4, new RGB(0, 0.2), null); + RenderUtils.quadBorder(rect, 2, new RGB(0, 0.3), null); + } + + // SCREWS + if (showScrews) { + int screw = 15; + + Rect screwRect = new Rect().grow_ip(8, 8); + + glPushMatrix(); + RenderUtils.translate(rect.getLeftBottom().add(screw, screw)); + glRotated(screws[0], 0, 0, 1); + RenderUtils.quadTextured(screwRect, Tx.SCREW); + glPopMatrix(); + + glPushMatrix(); + RenderUtils.translate(rect.getLeftTop().add(screw, -screw)); + glRotated(screws[1], 0, 0, 1); + RenderUtils.quadTextured(screwRect, Tx.SCREW); + glPopMatrix(); + + glPushMatrix(); + RenderUtils.translate(rect.getRightTop().sub(screw, screw)); + glRotated(screws[2], 0, 0, 1); + RenderUtils.quadTextured(screwRect, Tx.SCREW); + glPopMatrix(); + + glPushMatrix(); + RenderUtils.translate(rect.getRightBottom().add(-screw, screw)); + glRotated(screws[3], 0, 0, 1); + RenderUtils.quadTextured(screwRect, Tx.SCREW); + glPopMatrix(); + } + } +} diff --git a/src/net/tortuga/gui/widgets/menu/MenuButton.java b/src/net/tortuga/gui/widgets/menu/MenuButton.java new file mode 100644 index 0000000..ece5697 --- /dev/null +++ b/src/net/tortuga/gui/widgets/menu/MenuButton.java @@ -0,0 +1,147 @@ +package net.tortuga.gui.widgets.menu; + + +import static net.tortuga.gui.widgets.EColorRole.*; +import net.tortuga.fonts.Fonts; +import net.tortuga.fonts.LoadedFont; +import net.tortuga.gui.widgets.ETheme; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.sounds.Effects; +import net.tortuga.textures.Tx; +import net.tortuga.util.Align; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; + + +/** + * Clcikable button. + * + * @author MightyPork + */ +public class MenuButton extends Widget { + + /** + * new button + * + * @param id widget id + * @param text widget text + * @param font render font + */ + public MenuButton(int id, String text, LoadedFont font) { + setId(id); + setText(text); + setFont(font); + setTheme(ETheme.MENU_BUTTON); + } + + + /** + * new button + * + * @param id widget id + * @param text widget text + */ + public MenuButton(int id, String text) { + setId(id); + setText(text); + setFont(Fonts.menu_button); + setTheme(ETheme.MENU_BUTTON); + setMinSize(300, 72); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + boolean hover = (isMouseOver(mouse) && isPanelOnTop()); + + RGB color = getColor(FG, mouse, hover); + RGB blur = getColor(SHADOW, mouse, hover); + + /*double yOffset = clicked ? 72D / 256D : hover ? 144D / 256D : 0; + + Rect left = new Rect(rect.x1(), rect.y1(), rect.x1() + 13, rect.y2()); + Rect leftTx = new Rect(0D, 0, 13, 72).div_ip(256); + leftTx.add_ip(0, yOffset); + RenderUtils.quadRectTextured(left, leftTx, Textures.buttons); + + left = new Rect(rect.x1() + 13, rect.y1(), rect.x2() - 13, rect.y2()); + leftTx = new Rect(13D, 0, 243, 72).div_ip(256); + leftTx.add_ip(0, yOffset); + RenderUtils.quadRectTextured(left, leftTx, Textures.buttons); + + left = new Rect(rect.x2() - 13, rect.y1(), rect.x2(), rect.y2()); + leftTx = new Rect(243, 0, 256, 72).div_ip(256); + leftTx.add_ip(0, yOffset); + RenderUtils.quadRectTextured(left, leftTx, Textures.buttons);*/ + + //RenderUtils.quadRectBorder(rect, 1, RGB.GREEN, RGB.TRANSPARENT); + + int ofs = clicked ? 1 : hover ? 2 : 0; + RenderUtils.quadTexturedFrame(rect, Tx.BTN_MENU, ofs); + + font.drawFuzzy(rect.getCenter().sub(0, font.getHeight() / 2).round(), text, Align.CENTER, color, blur, 2, false); + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + if (button != 0) return null; + + if (!isMouseOver(pos)) { + if (clicked && !down) clicked = false; + return null; + } + if (down == true) { + clicked = true; + } else { + if (clicked) { + Effects.play("gui.button.menu"); + + clicked = false; + + // mouse down and up + // click consumed, send event to listener + return this; + } + clicked = false; + } + return null; + } + + + @Override + public void onBlur() + { + clicked = false; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + minSize.setMax(new Coord(font.getWidth(text) + 40, font.getHeight())); + rect.setTo(minSize); + + } + +} diff --git a/src/net/tortuga/gui/widgets/menu/MenuTitle.java b/src/net/tortuga/gui/widgets/menu/MenuTitle.java new file mode 100644 index 0000000..8f80fdd --- /dev/null +++ b/src/net/tortuga/gui/widgets/menu/MenuTitle.java @@ -0,0 +1,88 @@ +package net.tortuga.gui.widgets.menu; + + +import static net.tortuga.gui.widgets.EColorRole.*; +import net.tortuga.fonts.Fonts; +import net.tortuga.fonts.LoadedFont; +import net.tortuga.gui.widgets.ETheme; +import net.tortuga.gui.widgets.Widget; +import net.tortuga.util.Align; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; + + +/** + * Passive label + * + * @author MightyPork + */ +public class MenuTitle extends Widget { + + /** + * Gui label + * + * @param text widget text + * @param font render font + */ + public MenuTitle(String text, LoadedFont font) { + setText(text); + setFont(font); + setTheme(ETheme.MENU_TITLE); + } + + + /** + * Gui label menu_title + * + * @param text widget text + */ + public MenuTitle(String text) { + this(text, Fonts.menu_title); + } + + + @Override + public void render(Coord mouse) + { + if (!isVisible()) return; + + RGB color = getColor(FG, mouse); + RGB blur = getColor(SHADOW, mouse); + + font.drawFuzzy(rect.getCenterDown(), text, Align.CENTER, color, blur, 1); + + } + + + @Override + public Widget onMouseButton(Coord pos, int button, boolean down) + { + return null; + } + + + @Override + public Widget onScroll(Coord pos, int scroll) + { + return null; + } + + + @Override + public Widget onKey(int key, char chr, boolean down) + { + return null; + } + + + @Override + public void calcChildSizes() + { + //FontManager.setFullscreenDoubleSize(true); + setMinSize(font.getWidth(text), font.getHeight()); + rect.setTo(0, 0, minSize.x, minSize.y); + //FontManager.setFullscreenDoubleSize(false); + } + +} diff --git a/src/net/tortuga/input/EInput.java b/src/net/tortuga/input/EInput.java new file mode 100644 index 0000000..73bd540 --- /dev/null +++ b/src/net/tortuga/input/EInput.java @@ -0,0 +1,82 @@ +package net.tortuga.input; + + +import org.newdawn.slick.util.Log; + + +/** + * Enum of event types for trigger. + * + * @author MightyPork + */ +public enum EInput +{ + /** Keyboard key pressed */ + KEY_PRESS, + /** Keyboard key held down */ + KEY_DOWN, + /** Keyboard key not pressed */ + KEY_UP, + /** Keyboard key released */ + KEY_RELEASE, + /** Wheel scrolled */ + SCROLL, + /** Mouse button pressed */ + BTN_PRESS, + /** Mouse button held down */ + BTN_DOWN, + /** Mouse button not pressed */ + BTN_UP, + /** Mouse button released */ + BTN_RELEASE; + + /** + * Get state / change variant + * + * @param beStatic is long-lasting state (key held down) + * @return the modified variant. + */ + public EInput variant(boolean beStatic) + { + if (beStatic) { + switch (this) { + case BTN_PRESS: + case BTN_DOWN: + return BTN_DOWN; + case BTN_UP: + case BTN_RELEASE: + return BTN_UP; + case SCROLL: + return SCROLL; + case KEY_PRESS: + case KEY_DOWN: + return KEY_DOWN; + case KEY_UP: + case KEY_RELEASE: + return KEY_UP; + } + } else { + switch (this) { + case BTN_PRESS: + case BTN_DOWN: + return BTN_PRESS; + case BTN_UP: + case BTN_RELEASE: + return BTN_RELEASE; + case SCROLL: + return SCROLL; + case KEY_PRESS: + case KEY_DOWN: + return KEY_PRESS; + case KEY_UP: + case KEY_RELEASE: + return KEY_RELEASE; + } + } + + Log.warn("Invalid EInput constant in EInput.variant: " + this); + + return this; + + } +} diff --git a/src/net/tortuga/input/Function.java b/src/net/tortuga/input/Function.java new file mode 100644 index 0000000..bf99468 --- /dev/null +++ b/src/net/tortuga/input/Function.java @@ -0,0 +1,19 @@ +package net.tortuga.input; + + +/** + * Function object with return type + * + * @author MightyPork + * @param return type + */ +public interface Function { + + /** + * Execute this routine + * + * @param args optional arguments (not optional for implementations) + * @return returned value + */ + public T run(Object... args); +} diff --git a/src/net/tortuga/input/IInputHandler.java b/src/net/tortuga/input/IInputHandler.java new file mode 100644 index 0000000..04c790c --- /dev/null +++ b/src/net/tortuga/input/IInputHandler.java @@ -0,0 +1,52 @@ +package net.tortuga.input; + + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Vec; + + +/** + * Input event handler + * + * @author MightyPork + */ +public interface IInputHandler { + + /** + * Called each update tick, if the mouse position was changed. + * + * @param pos mouse position + * @param move mouse motion + * @param wheelDelta mouse wheel delta + */ + public void onMouseMove(Coord pos, Vec move, int wheelDelta); + + + /** + * Mouse event handler. + * + * @param button button which caused this event + * @param down true = down, false = up + * @param wheelDelta number of steps the wheel turned since last event + * @param pos mouse position + * @param deltaPos delta mouse position + */ + public void onMouseButton(int button, boolean down, int wheelDelta, Coord pos, Coord deltaPos); + + + /** + * Key event handler. + * + * @param key key index, constant Keyboard.KEY_??? + * @param c character typed, if any + * @param down true = down, false = up + */ + public void onKey(int key, char c, boolean down); + + + /** + * In this method screen can handle static inputs, that is: + * Keyboard.isKeyDown, Mouse.isButtonDown etc. + */ + public void handleStaticInputs(); +} diff --git a/src/net/tortuga/input/InputTrigger.java b/src/net/tortuga/input/InputTrigger.java new file mode 100644 index 0000000..f6c1d75 --- /dev/null +++ b/src/net/tortuga/input/InputTrigger.java @@ -0,0 +1,169 @@ +package net.tortuga.input; + + +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import com.porcupine.coord.Coord; +import com.porcupine.math.Calc; + + +/** + * Thing for testing input states and events. + * + * @author MightyPork + */ +public class InputTrigger { + + /** Trigger event */ + public EInput type = EInput.BTN_PRESS; + /** mark that this trigger is static */ + public boolean isStatic = false; + //KEY_DOWN, KEY_HOLD, KEY_UP, SCROLL, MOVE, BTN_DOWN, BTN_UP, BTN_HOLD; + private int keyIndex = -1; + private int scrollDir = -2; + private int buttonIndex = -1; + private int attrib_raw = -1; + + + /** + * Pack to bundle + * + * @return bundle + */ + public TriggerBundle toBundle() + { + return new TriggerBundle(type, attrib_raw); + } + + + /** + * Create trigger from bundle + * + * @param bundle bundle + */ + public InputTrigger(TriggerBundle bundle) { + this(bundle.event, bundle.attrib); + } + + + /** + * Set trigger attribute + * + * @param attrib attribute number (Key→index, Mouse→button, + * Scroll→direction) + */ + public void setAttrib(int attrib) + { + attrib_raw = attrib; + switch (type) { + case BTN_PRESS: + case BTN_DOWN: + case BTN_UP: + case BTN_RELEASE: + buttonIndex = attrib; + break; + + case KEY_PRESS: + case KEY_DOWN: + case KEY_UP: + case KEY_RELEASE: + keyIndex = attrib; + break; + + case SCROLL: + scrollDir = attrib; + break; + } + + if (type == EInput.BTN_DOWN || type == EInput.KEY_DOWN || type == EInput.BTN_UP || type == EInput.KEY_UP) isStatic = true; + } + + + /** + * New trigger - event tester + * + * @param type event type + * @param attrib (Key→index, Mouse→button, Scroll→direction) + */ + public InputTrigger(EInput type, int attrib) { + this.type = type; + setAttrib(attrib); + } + + + /** + * Try to trigger by mouse event. + * + * @param button button which caused this event + * @param down true = down, false = up + * @param wheelDelta number of steps the wheel turned since last event + * @param pos mouse position + * @param deltaPos delta mouse position + * @return was triggered + */ + public boolean onMouseButton(int button, boolean down, int wheelDelta, Coord pos, Coord deltaPos) + { + if (type == EInput.BTN_PRESS) { + return down && buttonIndex == button; + } + + if (type == EInput.BTN_RELEASE) { + return !down && buttonIndex == button; + } + + if (type == EInput.SCROLL) { + return Calc.sgn(wheelDelta) == Calc.sgn(scrollDir); + } + return false; + } + + + /** + * Try to trigger by keyboard event. + * + * @param key key index, constant Keyboard.KEY_??? + * @param c character typed, if any + * @param down true = down, false = up + * @return was triggered + */ + public boolean onKey(int key, char c, boolean down) + { + if (type == EInput.KEY_PRESS) { + return down && keyIndex == key; + } + + if (type == EInput.KEY_RELEASE) { + return !down && keyIndex == key; + } + return false; + } + + + /** + * Try to trigger by static inputs (held key/button) + * + * @return was triggered + */ + public boolean handleStaticInputs() + { + if (type == EInput.BTN_DOWN) { + return Mouse.isButtonDown(buttonIndex); + } + + if (type == EInput.KEY_DOWN) { + return Keyboard.isKeyDown(keyIndex); + } + + if (type == EInput.BTN_UP) { + return !Mouse.isButtonDown(buttonIndex); + } + + if (type == EInput.KEY_UP) { + return !Keyboard.isKeyDown(keyIndex); + } + + return false; + } + +} diff --git a/src/net/tortuga/input/InputTriggerGroup.java b/src/net/tortuga/input/InputTriggerGroup.java new file mode 100644 index 0000000..2c49202 --- /dev/null +++ b/src/net/tortuga/input/InputTriggerGroup.java @@ -0,0 +1,136 @@ +package net.tortuga.input; + + +import java.util.HashSet; +import java.util.Set; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Vec; + + +/** + * Group of input triggers, able to detect multiple events separately. + * + * @author MightyPork + */ +public class InputTriggerGroup implements IInputHandler { + + /** triggered actions */ + public Set triggers = new HashSet(); + + + /** + * Add new triggered action + * + * @param action the action + * @return the action trigger added + */ + public InputTriggeredAction addTrigger(InputTriggeredAction action) + { + InputTriggeredAction a; + triggers.add(a = action); + return a; + } + + + /** + * Add new triggered action + * + * @param triggers action trigger(s) + * @param func action + * @return the action trigger added + */ + public InputTriggeredAction addTrigger(Routine func, InputTrigger... triggers) + { + InputTriggeredAction a; + this.triggers.add(a = new InputTriggeredAction(func, triggers)); + return a; + } + + + /** + * Add new triggered action + * + * @param event event detected + * @param attrib event attribute + * @param func action + * @return the action trigger added + */ + public InputTriggeredAction addTrigger(Routine func, EInput event, int attrib) + { + InputTriggeredAction a; + triggers.add(a = new InputTriggeredAction(func, event, attrib)); + return a; + } + + + /** + * Add new triggered action + * + * @param func action + * @param event1 1st event type + * @param attrib1 1st event attriv + * @param event2 2nd event type + * @param attrib2 2nd type attrib + * @return the action trigger added + */ + public InputTriggeredAction addTrigger(Routine func, EInput event1, int attrib1, EInput event2, int attrib2) + { + InputTriggeredAction a; + triggers.add(a = new InputTriggeredAction(func, new InputTrigger(event1, attrib1), new InputTrigger(event2, attrib2))); + return a; + } + + + /** + * Remove trigger action (eg. to rebuild it) + * + * @param action the action. + */ + public void removeTrigger(InputTriggeredAction action) + { + if (action == null) return; + triggers.remove(action); + } + + + /** + * Remove all action triggers + */ + public void clearTriggers() + { + triggers.clear(); + } + + + @Override + public void onKey(int key, char c, boolean down) + { + for (InputTriggeredAction action : triggers) { + action.onKey(key, c, down); + } + } + + + @Override + public void onMouseButton(int button, boolean down, int wheelDelta, Coord pos, Coord deltaPos) + { + for (InputTriggeredAction action : triggers) { + action.onMouseButton(button, down, wheelDelta, pos, deltaPos); + } + } + + + @Override + public void onMouseMove(Coord pos, Vec move, int wheelDelta) + {} + + + @Override + public void handleStaticInputs() + { + for (InputTriggeredAction action : triggers) { + action.handleStaticInputs(); + } + } +} diff --git a/src/net/tortuga/input/InputTriggeredAction.java b/src/net/tortuga/input/InputTriggeredAction.java new file mode 100644 index 0000000..087cc35 --- /dev/null +++ b/src/net/tortuga/input/InputTriggeredAction.java @@ -0,0 +1,141 @@ +package net.tortuga.input; + + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Vec; + + +/** + * Function executor triggered by input event. + * + * @author MightyPork + */ +public class InputTriggeredAction implements IInputHandler { + + /** Trigger used to detect the event */ + private InputTrigger[] triggers = null; + /** Function called when triggered */ + private Routine function = null; + + + /** + * Function controller triggered by input event. + * + * @param event event detected + * @param attrib event attribute + * @param func function to run + */ + public InputTriggeredAction(Routine func, EInput event, int attrib) { + triggers = new InputTrigger[] { new InputTrigger(event, attrib) }; + function = func; + } + + + /** + * Function controller triggered by input event. + * + * @param func function to run + * @param triggers triggers (AND) + */ + public InputTriggeredAction(Routine func, InputTrigger... triggers) { + this.triggers = triggers; + function = func; + } + + + /** + * Replace function. + * + * @param func new function + */ + public void replaceFunction(Routine func) + { + this.function = null; + this.function = func; + } + + + /** + * Replace trigger(s) + * + * @param triggers the triggers + */ + public void setTriggers(InputTrigger... triggers) + { + this.triggers = null; + this.triggers = triggers; + } + + + /** + * Replace trigger + * + * @param event event detected + * @param attrib event attribute + */ + public void setTrigger(EInput event, int attrib) + { + this.triggers = null; + triggers = new InputTrigger[] { new InputTrigger(event, attrib) }; + } + + + @Override + public void onMouseMove(Coord pos, Vec move, int wheelDelta) + { + // NO-OP + } + + + @Override + public void onMouseButton(int button, boolean down, int wheelDelta, Coord pos, Coord deltaPos) + { + for (InputTrigger t : triggers) { + if (t.isStatic) { + if (!t.handleStaticInputs()) return; + } else { + if (!t.onMouseButton(button, down, wheelDelta, pos, deltaPos)) return; + + } + } + + function.run(); + } + + + @Override + public void onKey(int key, char c, boolean down) + { + for (InputTrigger t : triggers) { + if (t.isStatic) { + if (!t.handleStaticInputs()) return; + } else { + if (!t.onKey(key, c, down)) return; + } + } + + function.run(); + } + + + @Override + public void handleStaticInputs() + { + for (InputTrigger t : triggers) { + if (!t.handleStaticInputs()) return; + } + + function.run(); + } + + + /** + * Get all triggers + * + * @return triggers + */ + public InputTrigger[] getTriggers() + { + return triggers; + } +} diff --git a/src/net/tortuga/input/Keys.java b/src/net/tortuga/input/Keys.java new file mode 100644 index 0000000..0d55ebd --- /dev/null +++ b/src/net/tortuga/input/Keys.java @@ -0,0 +1,102 @@ +package net.tortuga.input; + + +import java.util.Arrays; + +import org.lwjgl.input.Keyboard; + + +/** + * Key state handler + */ +public class Keys { + + private static boolean[] prevKeys; + private static boolean[] keys; + + + /** + * initialize key state handler + */ + public static void init() + { + keys = new boolean[Keyboard.KEYBOARD_SIZE]; + Arrays.fill(keys, false); + prevKeys = new boolean[Keyboard.KEYBOARD_SIZE]; + Arrays.fill(prevKeys, false); + } + + + /** + * method called when key event was detected in Screen class. + * + * @param key + * @param down + */ + public static void onKey(int key, boolean down) + { + prevKeys[key] = keys[key]; + keys[key] = down; + } + + + /** + * Check if key is down + * + * @param key key index + * @return is down + */ + public static boolean isDown(int key) + { + return keys[key]; + } + + + /** + * Check if key is up + * + * @param key key index + * @return is up + */ + public static boolean isUp(int key) + { + return !keys[key]; + } + + + /** + * Check if key was just pressed (changed state since last event on this + * key) + * + * @param key key index + * @return true if changed state to DOWN + */ + public static boolean justPressed(int key) + { + return !prevKeys[key] && keys[key]; + } + + + /** + * Check if key was just released (changed state since last event on this + * key) + * + * @param key key index + * @return true if changed state to UP + */ + public static boolean justReleased(int key) + { + return prevKeys[key] && !keys[key]; + } + + + /** + * Destroy "just" flag for a key. + * + * @param key key index + */ + public static void destroyChangeState(int key) + { + prevKeys[key] = keys[key]; + } +} diff --git a/src/net/tortuga/input/Routine.java b/src/net/tortuga/input/Routine.java new file mode 100644 index 0000000..1f75d65 --- /dev/null +++ b/src/net/tortuga/input/Routine.java @@ -0,0 +1,15 @@ +package net.tortuga.input; + + +/** + * Stand-alone routine + * + * @author MightyPork + */ +public interface Routine { + + /** + * Execute this routine + */ + public void run(); +} diff --git a/src/net/tortuga/input/TriggerBundle.java b/src/net/tortuga/input/TriggerBundle.java new file mode 100644 index 0000000..b06eb7a --- /dev/null +++ b/src/net/tortuga/input/TriggerBundle.java @@ -0,0 +1,176 @@ +package net.tortuga.input; + + +import net.tortuga.util.ObjParser; + +import org.lwjgl.input.Keyboard; + + +/** + * Bundle representing input configuration for triggers. + * + * @author MightyPork + */ +public class TriggerBundle { + + /** Detected event */ + public EInput event; + /** Event attribute */ + public int attrib; + + + /** + * Create trigger bundle + * + * @param event event + * @param attrib event attribute + */ + public TriggerBundle(EInput event, int attrib) { + this.event = event; + this.attrib = attrib; + } + + + /** + * Create trigger bundle + * + * @param other other bundle + */ + public TriggerBundle(TriggerBundle other) { + this.event = other.event; + this.attrib = other.attrib; + } + + + // constructor + public TriggerBundle() {} + + + /** + * Get copy + * + * @return copy + */ + public TriggerBundle copy() + { + return new TriggerBundle(this); + } + + + /** + * Make the event static / dynamic + * + * @param beStatic is static [TRUE] (hold) or dynamic (press) + */ + public void setStatic(boolean beStatic) + { + event = event.variant(beStatic); + } + + + /** + * Get human readable variant of trigger bundle. + * + * @param longFmt long style (short: X, Left mouse, long: Press X, Left + * mouse down) + * @return human readable format + */ + public String getLabel(boolean longFmt) + { + String evt = null, name = null; + switch (event) { + case BTN_PRESS: + evt = "down"; + //$FALL-THROUGH$ + case BTN_DOWN: + if (evt == null) evt = "hold"; + //$FALL-THROUGH$ + case BTN_UP: + if (evt == null) evt = "unhold"; + //$FALL-THROUGH$ + case BTN_RELEASE: + if (evt == null) evt = "up"; + + // mouse. + if (attrib == 0) name = "Left mouse"; + if (attrib == 1) name = "Right mouse"; + if (attrib == 2) name = "Middle mouse"; + if (attrib > 2) name = "Mouse " + attrib; + + if (longFmt) name += " " + evt; + return name; + + case KEY_PRESS: + evt = "Press"; + //$FALL-THROUGH$ + case KEY_DOWN: + if (evt == null) evt = "Hold"; + //$FALL-THROUGH$ + case KEY_UP: + if (evt == null) evt = "Unhold"; + //$FALL-THROUGH$ + case KEY_RELEASE: + if (evt == null) evt = "Release"; + + name = Keyboard.getKeyName(attrib); + if (longFmt) name = evt + " " + name; + return name; + + case SCROLL: + return "Wheel " + (attrib < 0 ? "in" : "out"); + } + + return "???"; + } + + + /** + * Convert to actual trigger + * + * @return trigger + */ + public InputTrigger toTrigger() + { + return new InputTrigger(this); + } + + + @Override + public String toString() + { + return event + ":" + attrib; + } + + + /** + * Load from string representation + * + * @param string string variant EVENT:ATTRIB + * @return this + */ + public TriggerBundle fromString(String string) + { + if (string == null) return null; + String[] pts = string.split("[:]"); + event = EInput.valueOf(pts[0]); + attrib = ObjParser.getInteger(pts[1]); + return this; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof TriggerBundle)) return false; + + return ((TriggerBundle) obj).event == event && ((TriggerBundle) obj).attrib == attrib; + } + + + @Override + public int hashCode() + { + return event.hashCode() ^ attrib; + } +} diff --git a/src/net/tortuga/level/GameState.java b/src/net/tortuga/level/GameState.java new file mode 100644 index 0000000..b223d4c --- /dev/null +++ b/src/net/tortuga/level/GameState.java @@ -0,0 +1,11 @@ +package net.tortuga.level; + + +import net.tortuga.level.map.entities.mobile.ETurtle; + + +public class GameState { + + public static ETurtle turtleTheme = ETurtle.BLUE; + +} diff --git a/src/net/tortuga/level/LevelBundle.java b/src/net/tortuga/level/LevelBundle.java new file mode 100644 index 0000000..feca550 --- /dev/null +++ b/src/net/tortuga/level/LevelBundle.java @@ -0,0 +1,60 @@ +package net.tortuga.level; + + +import net.tortuga.level.map.TurtleMap; +import net.tortuga.level.map.TurtleMapDescriptor; +import net.tortuga.level.map.entities.mobile.ETurtle; +import net.tortuga.level.program.GrainList; +import net.tortuga.level.program.StoneList; + + +/** + * Level bundle + * + * @author MightyPork + */ +public class LevelBundle { // TODO make Ionizable + + /** Program length */ + public int progLength = 0; + + /** Number of labels allowed */ + public int progLabels = 0; + + /** Number of variables allowed */ + public int progVars = 0; + + /** List of grains allowed (except "GotoLabel" and "Var") */ + public GrainList progGrains = new GrainList(); + /** List of stones enabled (except "Label") */ + public StoneList progStones = new StoneList(); + + /** Turtle map */ + public TurtleMapDescriptor mapDescriptor; + + /** Built map, used to start a level */ + private TurtleMap builtMap; + + + /** + * Get the built map + * + * @return map + */ + public TurtleMap getBuiltMap() + { + if (builtMap == null) buildMap(); + return builtMap; + } + + + /** + * Build map + */ + public void buildMap() + { + builtMap = mapDescriptor.buildMap(ETurtle.DEFAULT); + } + + // TODO add map data and records to beat +} diff --git a/src/net/tortuga/level/LevelPars.java b/src/net/tortuga/level/LevelPars.java new file mode 100644 index 0000000..b9b46d8 --- /dev/null +++ b/src/net/tortuga/level/LevelPars.java @@ -0,0 +1,46 @@ +package net.tortuga.level; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.tortuga.CustomIonMarks; + +import com.porcupine.ion.AbstractIonMap; +import com.porcupine.ion.Ionizable; + + +public class LevelPars implements Ionizable { + + public static final String COUNT_STONES = "cnt.length"; + public static final String COUNT_PGM_TICKS = "cnt.ticks"; + public static final String COUNT_MOVES = "cnt.moves"; + public static final String COUNT_FOOD = "cnt.food"; + // FIXME + private AbstractIonMap pars = new AbstractIonMap() {}; + + + @Override + public void ionRead(InputStream in) throws IOException + { + // TODO Auto-generated method stub + + } + + + @Override + public void ionWrite(OutputStream out) throws IOException + { + // TODO Auto-generated method stub + + } + + + @Override + public byte ionMark() + { + return CustomIonMarks.LEVEL_PARS; + } + +} diff --git a/src/net/tortuga/level/TurtleController.java b/src/net/tortuga/level/TurtleController.java new file mode 100644 index 0000000..e1f3843 --- /dev/null +++ b/src/net/tortuga/level/TurtleController.java @@ -0,0 +1,133 @@ +package net.tortuga.level; + + +import net.tortuga.level.map.TurtleMap; +import net.tortuga.level.map.entities.mobile.EntityTurtle; +import net.tortuga.level.map.tiles.MapTile; + + +/** + * A turtle controller with access to level map + * + * @author MightyPork + */ +public class TurtleController { + + /** Turtle map */ + private TurtleMap map; + + /** Turtle in the map */ + private EntityTurtle turtle; + + + /** + * Create turtle controller + * + * @param map the map object + */ + public TurtleController(TurtleMap map) { + this.map = map; + this.turtle = map.getTurtle(); + this.turtle.ctrl = this; + } + + + /** + * Get if turtle is ready for next command + * + * @return is ready + */ + public boolean isReady() + { + return map.isMovementFinished(); + } + + + /** + * Make turtle go forward + */ + public void goForward() + { + turtle.goForward(); + } + + + /** + * Make turtle go backward + */ + public void goBackward() + { + turtle.goBackward(); + } + + + /** + * Make turtle turn CCW + */ + public void turnLeft() + { + turtle.turnLeft(); + } + + + /** + * Make turtle turn CW + */ + public void turnRight() + { + turtle.turnRight(); + } + + + /** + * Get tile in frint of turtle + * + * @return tile in front of turtle + */ + public MapTile getTileFront() + { + return turtle.getTileFront(); + } + + + /** + * Get tile behind turtle + * + * @return tile behind + */ + public MapTile getTileBack() + { + return turtle.getTileBack(); + } + + + /** + * Get tile front down + * + * @return tile front down + */ + public MapTile getTileFrontDown() + { + return turtle.getTileFrontDown(); + } + + + /** + * Handler for food eating by turtle + */ + public void onFoodEaten() + { + // TODO count foods + System.out.println("Food eaten."); + } + + + /** + * Called on turtle death (out of world) + */ + public void onTurtleDied() + { + // TODO impl onTurtleDied + } + +} diff --git a/src/net/tortuga/level/TurtleEndReachedException.java b/src/net/tortuga/level/TurtleEndReachedException.java new file mode 100644 index 0000000..0c0f1d2 --- /dev/null +++ b/src/net/tortuga/level/TurtleEndReachedException.java @@ -0,0 +1,29 @@ +package net.tortuga.level; + + +/** + * Turtle program ended exception + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class TurtleEndReachedException extends Exception { + + public TurtleEndReachedException() {} + + + public TurtleEndReachedException(String message) { + super(message); + } + + + public TurtleEndReachedException(Throwable cause) { + super(cause); + } + + + public TurtleEndReachedException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/tortuga/level/TurtleRuntimeEnvironment.java b/src/net/tortuga/level/TurtleRuntimeEnvironment.java new file mode 100644 index 0000000..95de862 --- /dev/null +++ b/src/net/tortuga/level/TurtleRuntimeEnvironment.java @@ -0,0 +1,247 @@ +package net.tortuga.level; + + +import java.util.*; + +import net.tortuga.Constants; +import net.tortuga.level.program.tiles.CompiledStone; +import net.tortuga.level.program.tiles.ProgTileStone; +import net.tortuga.level.program.tiles.stones.StoneLabel; + + +/** + * Turtle runtime environment
+ * Executes program sequence and handles jumps and variables. + * + * @author MightyPork + */ +public class TurtleRuntimeEnvironment { + + /** compiled stone sequence */ + public List stones = new ArrayList(); + + /** variable map */ + public Map variables = new HashMap(); + + private Stack stack = new Stack(); + + /** Last executed stone */ + public CompiledStone lastStone = null; + + /** Last compare tile TOP grain value */ + public int compareTop = 0; + + /** Last compare tile BOTTOM grain value */ + public int compareBottom = 0; + + /** PC points before the next command (N-1) */ + public int pc = -1; + + private int sleepTimer = 0; + + /** Turtle controller */ + private TurtleController turtle; + + + /** + * new Turtle environment + * + * @param stones program sequence + * @param turtle the turtle + */ + public TurtleRuntimeEnvironment(List stones, TurtleController turtle) { + this.stones = stones; + this.turtle = turtle; + } + + + /** + * Add sleep time for the turtle + * + * @param seconds seconds to sleep + */ + public void addSleep(double seconds) + { + sleepTimer = (int) (Constants.FPS_GUI * seconds); + } + + + /** + * Get program length + * + * @return program length + */ + public int getLength() + { + return stones.size(); + } + + + /** + * Get compiled stone at address + * + * @param address address + * @return compiled stone + */ + public CompiledStone getStoneAt(int address) + { + return stones.get(address); + } + + + /** + * Jump to address + * + * @param address address + */ + public void jumpAbsolute(int address) + { + pc = address - 1; + } + + + /** + * Jump relative + * + * @param add steps to add + */ + public void jumpRelative(int add) + { + pc += add; + } + + + /** + * Goto start + */ + public void jumpStart() + { + pc = -1; + } + + + /** + * Goto end (stop program next cycle) + */ + public void jumpEnd() + { + pc = getLength() - 1; + } + + + /** + * Get varianble + * + * @param index variable number + * @return it's value + */ + public int getVariable(int index) + { + Integer val = variables.get(index); + if (val == null) return 0; + return val; + } + + + /** + * Set variable + * + * @param index variable number + * @param value it's new value + */ + public void setVariable(int index, int value) + { + variables.put(index, value); + } + + + /** + * Goto a label + * + * @param variant label index + * @throws TurtleRuntimeException + */ + public void jumpToLabel(int variant) throws TurtleRuntimeException + { + for (int i = 0; i < stones.size(); i++) { + CompiledStone cs = getStoneAt(i); + if (cs == null) continue; + + ProgTileStone stone = cs.getStone(); + if (stone instanceof StoneLabel) { + if (stone.getVariant() == variant) { + jumpAbsolute(i + 1); + break; + } + } + + } + + throw new TurtleRuntimeException("Jump to undefined label."); + } + + + /** + * Do next command + * + * @throws TurtleEndReachedException when program has ended + * @throws TurtleRuntimeException other errors + */ + public void doNext() throws TurtleEndReachedException, TurtleRuntimeException + { + // execution of the sleep command + if (sleepTimer > 0) { + sleepTimer--; + return; + } + + if (!turtle.isReady()) return; // waiting for animation + + pc++; // increment the PC + + if (pc < 0 || pc > getLength()) { + throw new TurtleRuntimeException("Jump address out of range."); + } + + if (pc == getLength()) { + throw new TurtleEndReachedException("The program has ended."); + } + + CompiledStone stone = getStoneAt(pc); + + lastStone = stone; + + if (stone == null) { + pc++; + doNext(); + return; + } + + stone.execute(this, turtle); + } + + + /** + * Push command address + */ + public void pushAddress() + { + stack.push(pc); + } + + + /** + * Pop command address + * + * @throws TurtleRuntimeException when stack is empty + */ + public void popAddress() throws TurtleRuntimeException + { + try { + pc = stack.pop(); + } catch (EmptyStackException e) { + throw new TurtleRuntimeException("Return outside function call."); + } + } + +} diff --git a/src/net/tortuga/level/TurtleRuntimeException.java b/src/net/tortuga/level/TurtleRuntimeException.java new file mode 100644 index 0000000..964b6f6 --- /dev/null +++ b/src/net/tortuga/level/TurtleRuntimeException.java @@ -0,0 +1,29 @@ +package net.tortuga.level; + + +/** + * Turtle runtime exception (error in user program) + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class TurtleRuntimeException extends Exception { + + public TurtleRuntimeException() {} + + + public TurtleRuntimeException(String message) { + super(message); + } + + + public TurtleRuntimeException(Throwable cause) { + super(cause); + } + + + public TurtleRuntimeException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/tortuga/level/map/EntityDescriptorList.java b/src/net/tortuga/level/map/EntityDescriptorList.java new file mode 100644 index 0000000..81a2eb8 --- /dev/null +++ b/src/net/tortuga/level/map/EntityDescriptorList.java @@ -0,0 +1,29 @@ +package net.tortuga.level.map; + + +import net.tortuga.CustomIonMarks; +import net.tortuga.level.map.entities.EntityDescriptor; + +import com.porcupine.ion.AbstractIonList; + + +public class EntityDescriptorList extends AbstractIonList { + + public EntityDescriptorList() {} + + + @Override + public byte ionMark() + { + return CustomIonMarks.ENTITY_LIST; + } + + + public void populateMap(TurtleMap map) + { + for (EntityDescriptor ed : this) { + map.addEntity(ed.getEntity()); + } + } + +} diff --git a/src/net/tortuga/level/map/ISwitch.java b/src/net/tortuga/level/map/ISwitch.java new file mode 100644 index 0000000..6d96272 --- /dev/null +++ b/src/net/tortuga/level/map/ISwitch.java @@ -0,0 +1,16 @@ +package net.tortuga.level.map; + + +/** + * Interface for toggleable tiles + * + * @author MightyPork + */ +public interface ISwitch { + + /** + * Is switch no + */ + public boolean isOn(); + +} diff --git a/src/net/tortuga/level/map/TileGridBuilder.java b/src/net/tortuga/level/map/TileGridBuilder.java new file mode 100644 index 0000000..e1c1017 --- /dev/null +++ b/src/net/tortuga/level/map/TileGridBuilder.java @@ -0,0 +1,51 @@ +package net.tortuga.level.map; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.util.Log; + +import com.porcupine.coord.CoordI; + + +public class TileGridBuilder { + + private int[][][] map; + + + public TileGridBuilder(CoordI size) { + this(size.x, size.y, size.z); + } + + + public TileGridBuilder(int x, int y, int z) { + map = new int[z][y][x]; + } + + + public void setTile(CoordI coord, int id) + { + setTile(coord.x, coord.y, coord.z, id); + } + + + public void setTile(int x, int y, int z, int id) + { + try { + map[z][y][x] = id; + } catch (ArrayIndexOutOfBoundsException e) { + Log.e("Coord [" + x + ";" + y + ";" + z + "] out of range in TurtleMapBuilder.", e); + } + } + + + public int[][][] toIdGrid() + { + return map; + } + + + public MapTile[][][] toTileGrid() + { + return new TileGridDescriptor(map).buildTileGrid(); + } +} diff --git a/src/net/tortuga/level/map/TileGridDescriptor.java b/src/net/tortuga/level/map/TileGridDescriptor.java new file mode 100644 index 0000000..96439bb --- /dev/null +++ b/src/net/tortuga/level/map/TileGridDescriptor.java @@ -0,0 +1,122 @@ +package net.tortuga.level.map; + + +import java.io.InputStream; +import java.io.OutputStream; + +import net.tortuga.CustomIonMarks; +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTileRegistry; +import net.tortuga.util.IonCoordI; + +import com.porcupine.ion.AbstractIonList; +import com.porcupine.ion.Ion; + + +/** + * Bundle of level map tiles, ionizable integer array with dimensions. + * + * @author MightyPork + */ +public class TileGridDescriptor extends AbstractIonList { + + /** Size of level map */ + public IonCoordI size = new IonCoordI(); + + + /** + * Level tile array, empty constructor + */ + public TileGridDescriptor() {} + + + @Override + public byte ionMark() + { + return CustomIonMarks.TILE_LIST; + } + + + /** + * Build from 3D array of IDs + * + * @param tiles array of IDs (z,y,x) + */ + public TileGridDescriptor(int[][][] tiles) { + size = new IonCoordI(tiles[0][0].length, tiles[0].length, tiles.length); + + for (int z = 0; z < tiles.length; z++) { + for (int y = 0; y < tiles[0].length; y++) { + for (int x = 0; x < tiles[0][0].length; x++) { + add(tiles[z][y][x]); + } + } + } + + } + + + /** + * Convert to a 3D array of IDs + * + * @return the array + */ + public int[][][] buildIdGrid() + { + int[][][] map = new int[size.z][size.y][size.x]; + + int index = 0; + for (int z = 0; z < size.z; z++) { + for (int y = 0; y < size.y; y++) { + for (int x = 0; x < size.x; x++) { + map[z][y][x] = get(index); + index++; + } + } + } + + return map; + } + + + /** + * Build map tile array (real tiles and nulls) + * + * @return tile array 3D + */ + public MapTile[][][] buildTileGrid() + { + MapTile[][][] map = new MapTile[size.z][size.y][size.x]; + + try { + int index = 0; + for (int z = 0; z < size.z; z++) { + for (int y = 0; y < size.y; y++) { + for (int x = 0; x < size.x; x++) { + map[z][y][x] = MapTileRegistry.getTile(get(index)); + index++; + } + } + } + } catch (Exception e) { + throw new RuntimeException("Could not load turtle map.", e); + } + + return map; + } + + + @Override + public void ionWriteCustomData(OutputStream out) + { + Ion.toStream(out, size); + } + + + @Override + public void ionReadCustomData(InputStream in) + { + size = (IonCoordI) Ion.fromStream(in); + } + +} diff --git a/src/net/tortuga/level/map/TurtleMap.java b/src/net/tortuga/level/map/TurtleMap.java new file mode 100644 index 0000000..5b7b7c2 --- /dev/null +++ b/src/net/tortuga/level/map/TurtleMap.java @@ -0,0 +1,663 @@ +package net.tortuga.level.map; + + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import net.tortuga.App; +import net.tortuga.level.TurtleController; +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MoveDir; +import net.tortuga.level.map.entities.mobile.ETurtle; +import net.tortuga.level.map.entities.mobile.EntityTurtle; +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTileRegistry; +import net.tortuga.util.Log; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; + + +/** + * A turtle map (X*Y, Z layers) + * + * @author MightyPork + */ +public class TurtleMap { + + // TODO make Ionizable + + /** + * Option to wait for ALL entities to finish movement before letting turtle + * to do next move. + */ + private static final boolean STRICT_WAITING = false; + + private CoordI size = new CoordI(0, 0, 0); + + /** Tile marked as Goal tile */ + private CoordI goal = new CoordI(0, 0, 0); + + /** Flag that a goal mark is present */ + public boolean hasGoal = false; + + private MapTile[][][] map = null; + + private ArrayList entities = new ArrayList(); + private ArrayList switches = new ArrayList(); + + /** Map's turtle */ + public EntityTurtle turtle = null; + + /** A turtle controller */ + public TurtleController controller; + + + /** + * Set map data, call hooks on all tiles + * + * @param map the map + */ + private void setMapData(MapTile[][][] map) + { + this.map = map; + size.setTo(map[0][0].length, map[0].length, map.length); + + for (int z = 0; z < size.z; z++) { + for (int y = 0; y < size.y; y++) { + for (int x = 0; x < size.x; x++) { + CoordI here = new CoordI(x, y, z); + MapTile tile = getTile(here); + if (tile != null) tile.onAddedToMap(this, here); + } + } + } + } + + + /** + * Set the turtle + * + * @param dir turtle look direction + * @param theme turtle spritesheet texture + * @param pos turtle coord + */ + public void setTurtle(MoveDir dir, ETurtle theme, CoordI pos) + { + if (turtle != null) { + if (entities.contains(turtle)) { + entities.remove(turtle); + } + } + turtle = new EntityTurtle(theme); + turtle.direction = dir; + turtle.pos = pos; + if (!entities.contains(turtle)) { + entities.add(turtle); + } + + turtle.map = this; + + controller = new TurtleController(this); + + turtle.onAddedToMap(); + } + + + /** + * Set turtle theme + * + * @param theme theme to set + */ + public void setTurtleTheme(ETurtle theme) + { + if (turtle == null) Log.w("Null turtle in TurtleMap!"); + turtle.setTheme(theme); + } + + + /** + * Get the turtle + * + * @return turtle + */ + public EntityTurtle getTurtle() + { + return turtle; + } + + + /** + * Get the turtle controller + * + * @return turtle + */ + public TurtleController getTurtleController() + { + return controller; + } + + + /** + * New turtle map + * + * @param width size X + * @param height size Y + * @param layers size Z (layers) + */ + public TurtleMap(int width, int height, int layers) { + map = new MapTile[layers][height][width]; + size.setTo(width, height, layers); + } + + + /** + * New turtle map + * + * @param size map size + */ + public TurtleMap(CoordI size) { + map = new MapTile[size.z][size.y][size.x]; + size.setTo(size); + } + + + /** + * New turtle map + * + * @param levelData level data to be used in this map + */ + public TurtleMap(MapTile[][][] levelData) { + setMapData(levelData); + } + + + /** + * Add an entity + * + * @param entity entity + */ + public void addEntity(Entity entity) + { + entities.add(entity); + entity.setMap(this); + entity.onAddedToMap(); + } + + + /** + * Add an entity + * + * @param entity entity + * @param pos entity position + */ + public void addEntity(Entity entity, CoordI pos) + { + entity.setPos(pos); + addEntity(entity); + } + + + /** + * Add an entity + * + * @param entity entity + * @param x pos x + * @param y pos y + * @param z pos z + */ + public void addEntity(Entity entity, int x, int y, int z) + { + addEntity(entity, new CoordI(x, y, z)); + } + + + /** + * Remove entity from list + * + * @param entity entity + */ + public void removeEntity(Entity entity) + { + entities.remove(entity); + } + + + /** + * Get if entity is registered.
+ * Note: Dead entities are removed from the list + * + * @param entity entity to check + * @return is in list + */ + public boolean hasEntity(Entity entity) + { + return entities.contains(entity); + } + + + /** + * Get map tile at Coord + * + * @param x coord X + * @param y coord Y + * @param z layer index (Z) + * @return the tile (null for empty tile) + */ + public MapTile getTile(int x, int y, int z) + { + if (x < 0 || y < 0 || z < 0) return null; + if (x >= size.x || y >= size.y || z >= size.z) return null; + return map[z][y][x]; + } + + + /** + * Get if a map tile at Coord is solid and not null + * + * @param x coord X + * @param y coord Y + * @param z layer index (Z) + * @return is solid + */ + public boolean isTileSolid(int x, int y, int z) + { + if (x < 0 || y < 0 || z < 0) return false; + if (x >= size.x || y >= size.y || z >= size.z) return false; + return map[z][y][x] != null && map[z][y][x].isSolidForCollision(this, new CoordI(x, y, z)); + } + + + /** + * Get if a map tile at Coord is opaque and not null + * + * @param x coord X + * @param y coord Y + * @param z layer index (Z) + * @return is solid + */ + public boolean isTileFullCube(int x, int y, int z) + { + if (x < 0 || y < 0 || z < 0) return false; + if (x >= size.x || y >= size.y || z >= size.z) return false; + return map[z][y][x] != null && map[z][y][x].isShadedAsCube(this, new CoordI(x, y, z)); + } + + + /** + * Get map tile at Coord + * + * @param pos coord + * @return the tile (null for empty tile) + */ + public MapTile getTile(CoordI pos) + { + return getTile(pos.x, pos.y, pos.z); + } + + + /** + * Get if a map tile at Coord is solid and not null + * + * @param pos coord + * @return is solid + */ + public boolean isTileSolid(CoordI pos) + { + return isTileSolid(pos.x, pos.y, pos.z); + } + + + /** + * Get if a map tile at Coord is opaque and not null + * + * @param pos coord + * @return is solid + */ + public boolean isTileFullCube(CoordI pos) + { + return isTileFullCube(pos.x, pos.y, pos.z); + } + + + /** + * Set map tile at Coord + * + * @param x coord X + * @param y coord Y + * @param z layer index (Z) + * @param tile the tile (null for empty tile) + * @return true on success (false if coords are outside the map) + */ + public boolean setTile(int x, int y, int z, MapTile tile) + { + if (x < 0 || y < 0 || z < 0) return false; + if (x >= size.x || y >= size.y || z >= size.z) return false; + + map[z][y][x] = tile; + + if (tile != null) tile.onAddedToMap(this, new CoordI(x, y, z)); + return true; + } + + + /** + * Set map tile at Coord + * + * @param pos coord + * @param tile the tile (null for empty tile) + * @return true on success (false if coords are outside the map) + */ + public boolean setTile(CoordI pos, MapTile tile) + { + return setTile(pos.x, pos.y, pos.z, tile); + } + + + /** + * Set map tile at Coord + * + * @param x coord X + * @param y coord Y + * @param z layer index (Z) + * @param id the tile ID (0 for empty tile) + * @return true on success (false if coords are outside the map) + */ + public boolean setTile(int x, int y, int z, int id) + { + return setTile(x, y, z, MapTileRegistry.getTile(id)); + } + + + /** + * Set map tile at Coord + * + * @param pos coord + * @param id the tile id (0 for empty tile) + * @return true on success (false if coords are outside the map) + */ + public boolean setTile(CoordI pos, int id) + { + return setTile(pos.x, pos.y, pos.z, id); + } + + + /** + * Set map goal tile coord + * + * @param pos goal coord + */ + public void setGoal(CoordI pos) + { + if (pos == null) { + hasGoal = false; + } else { + setGoal(pos.x, pos.y, pos.z); + } + } + + + /** + * Set map goal tile coord + * + * @param x x coord + * @param y y coord + * @param z z coord (layer) + */ + public void setGoal(int x, int y, int z) + { + goal = new CoordI(x, y, z); + hasGoal = true; + } + + + /** + * Get goal coord + * + * @return goal coord + */ + public CoordI getGoal() + { + if (!hasGoal) return null; + + return goal; + } + + + /** + * Get map size + * + * @return size + */ + public CoordI getSize() + { + return size; + } + + + /** + * Get living entities at given coord + * + * @param pos cord to check + * @return entities there + */ + public Set getEntitiesAtCoord(CoordI pos) + { + Set found = new HashSet(); + + for (Entity ent : entities) { + if (!ent.isDead()) { + if (ent.getCoord().equals(pos)) { + found.add(ent); + } + } + } + + return found; + } + + + /** + * Render this map + * + * @param mapMin map widget min coord + */ + public void render(Coord mapMin) + { + // update entities, delete dead + for (int i = 0; i < entities.size(); i++) { + Entity ent = entities.get(i); + if (!(ent instanceof EntityTurtle)) { + ent.updatePos(App.currentDelta); + if (ent.isDead()) { + entities.remove(i); + i--; + } + } + } + + for (int y = 0; y < size.y; y++) { + for (int z = 0; z < size.z; z++) { + for (int x = 0; x < size.x; x++) { + CoordI here = new CoordI(x, y, z); + MapTile tile = getTile(here); + if (tile != null) tile.update(this, here, App.currentDelta); + } + } + } + + turtle.updatePos(App.currentDelta); + + int toRender = entities.size(); + // render entities out of map: left, top, below + // not rendered entities: rendered = false; + for (Entity ent : entities) { + if (ent.getRenderCoord().x < -1 || ent.getRenderCoord().y < -1 || ent.getRenderCoord().z < 0) { + ent.render(mapMin); + ent.rendered = true; + toRender--; + } else { + ent.rendered = false; + } + } + + for (int y = -1; y < size.y + 1; y++) { + for (int z = 0; z < size.z + 4; z++) { + for (int x = -1; x < size.x + 1; x++) { + CoordI here = new CoordI(x, y, z); + MapTile tile = getTile(here); + + // flag if entities should be hidden + boolean isOpaque = false; + + // render non-transparent tile before entities + if (tile != null) { + tile.render(mapMin, here, this); + isOpaque = tile.isShadedAsCube(this, here) && !tile.isGlassy(this, here); + } + + if (toRender <= 0) continue; // all rendered, no more work. + + // if turtle heading south, render before entities + if (!turtle.rendered && turtle.inMotion && turtle.moveDir == MoveDir.SOUTH) { + if (turtle.getRenderCoord().equals(here)) { + if (!isOpaque) turtle.render(mapMin); + turtle.rendered = true; + toRender--; + } + + } + + // render entities + for (Entity ent : entities) { + if (!ent.rendered && ent != turtle && ent.getRenderCoord().equals(here)) { + if (!isOpaque) ent.render(mapMin); + ent.rendered = true; + toRender--; + } + } + + // if turtle not going south, render after entities + if (!turtle.rendered && turtle.getRenderCoord().equals(here)) { + if (!isOpaque) turtle.render(mapMin); + turtle.rendered = true; + toRender--; + + } + + } + } + } + + // render entities on the other side of the map + for (Entity ent : entities) { + if (!ent.rendered) { + if (ent.getRenderCoord().x > size.x || ent.getRenderCoord().y > size.y || ent.getRenderCoord().z > size.z + 3) { + ent.render(mapMin); + ent.rendered = true; + toRender--; + } + } + } + } + + +// /** +// * Update entities +// */ +// public void update() { +// for (EntityBase ent : entities) { +// ent.update(); +// } +// } + + /** + * Get a copy + * + * @return copy + */ + public TurtleMap copy() + { + TurtleMap tm = new TurtleMap(size.x, size.y, size.z); + tm.setGoal(goal); + + for (int z = 0; z < size.z; z++) { + for (int y = 0; y < size.y; y++) { + for (int x = 0; x < size.x; x++) { + MapTile tile = getTile(x, y, z); + if (tile != null) tm.setTile(x, y, z, tile.copy()); + } + } + } + + tm.setTurtle(turtle.direction, turtle.theme, turtle.pos.copy()); + + for (Entity ent : entities) { + if (!(ent instanceof EntityTurtle)) tm.addEntity(ent.copy()); + } + + return tm; + } + + + /** + * Get if all moving entities in the map are stopped or dead + * + * @return is ready for next turtle step + */ + public boolean isMovementFinished() + { + if (!turtle.isMoveFinished()) return false; + + if (STRICT_WAITING) { + for (Entity e : entities) { + if (!e.isMoveFinished()) return false; + } + } + + return true; + } + + + public boolean isTurtleDead() + { + return turtle.isDead(); + } + + + public boolean areAllSwitchesOn() + { + for (ISwitch sw : switches) { + if (!sw.isOn()) return false; + } + return true; + } + + + public boolean areAllSwitchesOff() + { + for (ISwitch sw : switches) { + if (sw.isOn()) return false; + } + return true; + } + + + public boolean isTurtleAtGoal() + { + return turtle.getCoord().equals(goal) && hasGoal; + } + + + /** + * Register a switch to this map + * + * @param tileSwitch registered switch device + */ + public void registerSwitch(ISwitch tileSwitch) + { + if (!switches.contains(tileSwitch)) switches.add(tileSwitch); + } + +} diff --git a/src/net/tortuga/level/map/TurtleMapDescriptor.java b/src/net/tortuga/level/map/TurtleMapDescriptor.java new file mode 100644 index 0000000..cf1507c --- /dev/null +++ b/src/net/tortuga/level/map/TurtleMapDescriptor.java @@ -0,0 +1,113 @@ +package net.tortuga.level.map; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import net.tortuga.CustomIonMarks; +import net.tortuga.level.map.entities.EntityDescriptor; +import net.tortuga.level.map.entities.TurtleDescriptor; +import net.tortuga.level.map.entities.mobile.ETurtle; +import net.tortuga.util.IonCoordI; + +import com.porcupine.coord.CoordI; +import com.porcupine.ion.Ion; +import com.porcupine.ion.Ionizable; + + +/** + * Level map descriptor (including entities, tiles and goal mark) + * + * @author MightyPork + */ +public class TurtleMapDescriptor implements Ionizable { + + private TurtleDescriptor turtle = null; + private IonCoordI goal = new IonCoordI(); + private boolean hasGoal = false; + private TileGridDescriptor mapTiles = null; + private EntityDescriptorList entities = null; + + + /** + * Level map descriptor - empty constructor + */ + public TurtleMapDescriptor() {} + + + /** + * Level map descriptor + * + * @param turtle tirtle coord + * @param goal goal coord + * @param hasGoal flag if goal tile should be shown + * @param mapTiles 3D array of map tiles + * @param entities list of entity descriptors + */ + public TurtleMapDescriptor(TurtleDescriptor turtle, CoordI goal, boolean hasGoal, int[][][] mapTiles, List entities) { + this.turtle = turtle; + if (goal == null) { + goal = new IonCoordI(); + hasGoal = false; + } + this.goal = new IonCoordI(goal); + this.hasGoal = hasGoal; + this.mapTiles = new TileGridDescriptor(mapTiles); + this.entities = new EntityDescriptorList(); + this.entities.addAll(entities); + } + + + @Override + public void ionRead(InputStream in) throws IOException + { + turtle = (TurtleDescriptor) Ion.fromStream(in); + goal = (IonCoordI) Ion.fromStream(in); + hasGoal = (Boolean) Ion.fromStream(in); + mapTiles = (TileGridDescriptor) Ion.fromStream(in); + entities = (EntityDescriptorList) Ion.fromStream(in); + } + + + @Override + public void ionWrite(OutputStream out) throws IOException + { + Ion.toStream(out, turtle); + Ion.toStream(out, goal); + Ion.toStream(out, hasGoal); + Ion.toStream(out, mapTiles); + Ion.toStream(out, entities); + } + + + /** + * Convert to a level map + * + * @param turtleTheme + * @return the map + */ + public TurtleMap buildMap(ETurtle turtleTheme) + { + TurtleMap map = new TurtleMap(mapTiles.buildTileGrid()); + + map.setTurtle(turtle.getDir(), turtleTheme, turtle.getPos()); + + map.setGoal(hasGoal ? goal : null); + + for (EntityDescriptor e : entities) { + map.addEntity(e.getEntity()); + } + + return map; + } + + + @Override + public byte ionMark() + { + return CustomIonMarks.MAP_DESCRIPTOR; + } + +} diff --git a/src/net/tortuga/level/map/entities/Entity.java b/src/net/tortuga/level/map/entities/Entity.java new file mode 100644 index 0000000..0500cac --- /dev/null +++ b/src/net/tortuga/level/map/entities/Entity.java @@ -0,0 +1,1269 @@ +package net.tortuga.level.map.entities; + + +import java.util.Random; +import java.util.Set; + +import net.tortuga.annotations.Unimplemented; +import net.tortuga.level.map.TurtleMap; +import net.tortuga.level.map.entities.mobile.EntityBlock; +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.mechanisms.TileElevator; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.AnimDouble; +import net.tortuga.util.Log; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.coord.Rect; +import com.porcupine.math.Calc; + + +/** + * Base class for map entities + * + * @author MightyPork + */ +public abstract class Entity { + + /** + * Hook called when entity is added to map + */ + public void onAddedToMap() + { + tryToFall(); + } + + // animation constants + + /** default horizontal animation time */ + public static final double ANIM_TIME_HORIZONTAL = 0.8; + + /** default rotate animation time */ + public static final double ANIM_TIME_ROTATE = 0.7; + + /** default horizontal slide animation time */ + public static final double ANIM_TIME_SLIDE = 0.4; //0.36; + + /** Random number generator */ + public static Random rand = new Random(); + + /** Flag that entity is dead and should be removed from list */ + private boolean dead = false; + + /** Flag that this entity is carried on a block. */ + public boolean isCarried; + + /** Flag that this entity was rendered during a render loop */ + public boolean rendered = false; + + /** Entity center position (tile in map) */ + public CoordI pos = new CoordI(0, 0, 0); + + /** The entity's map */ + public TurtleMap map; + + // movement variables + + /** Entity facing direction */ + public MoveDir direction = MoveDir.EAST; + + /** Direction of movement */ + public MoveDir moveDir = MoveDir.EAST; + + /** Last move direction (may be used) */ + public MoveDir moveDirLast = MoveDir.EAST; + + /** Entity move progress (0-1) */ + public AnimDouble moveProgress = new AnimDouble(0); + + /** Array of doubles indicating move trigger points */ + private double[] moveTriggers = new double[0]; + + /** Array of flags that move triggers were passed */ + private boolean[] moveTriggersPassed = new boolean[0]; + + /** Flag that entity is moving forward */ + public boolean inMotion = false; + + /** Status that the block is sliding */ + public boolean isSliding = false; + + /** Number of steps falling (gets cleared on floor) */ + public int stepsFalling = 0; + + /** + * Flag that shadow shall be rendered right under the entity, not at the + * nearest non-transparent cube + */ + public boolean useImmediateShadow = false; + + /** Direction of rotation */ + public RotateDir rotateDir = RotateDir.CW; + + /** Flag that entity is in rotation */ + public boolean inRotation = false; + + /** Entity rotation progress (0-1) */ + public AnimDouble rotateProgress = new AnimDouble(0); + + + /** + * Set immediate shadow flag - false = normal, true = shadow fixed to entity + * + * @param state shadow immediate + */ + public void setImmediateShadow(boolean state) + { + useImmediateShadow = state; + } + + + /** + * Animate fall + */ + public void animateFall() + { + moveProgress.setTo(0); + moveProgress.animIn(getAnimTimeFall(stepsFalling)); + } + + + /** + * Animate horizontal walking step + */ + public void animateHorizontal() + { + moveProgress.setTo(0); + moveProgress.animIn(getAnimTimeHorizontal()); + } + + + /** + * Animate rotation + */ + public void animateRotate() + { + rotateProgress.setTo(0); + rotateProgress.animIn(getAnimTimeRotate()); + } + + + /** + * Animate horizontal slide + */ + public void animateSlide() + { + moveProgress.setTo(0); + moveProgress.animIn(getAnimTimeSlide()); + } + + + /** + * Get if the entity can legally go to a given direction + * + * @param dir probed direction + * @return can go there + */ + public boolean canGoToDir(MoveDir dir) + { + CoordI coord = getCoord(); + + CoordI move = dir.getMoveVector(); + + CoordI posFront = coord.add(move); + CoordI posFront2 = coord.add(move.mul(2, 2)); + MapTile tileFront = map.getTile(posFront); + + // non-solid block or air + if (tileFront == null) return true; + + if (tileFront instanceof TileElevator) { + Set ents = map.getEntitiesAtCoord(posFront); + if (!ents.isEmpty()) return false; // && !((TileElevator)tileFront).inMotion; + } + + if (!tileFront.isSolidForCollision(map, posFront)) return true; + + if (isSliding) return false; + + if (!canPushBlocks()) return false; + + // try to push. + + // get tile past front tile (should be empty) + MapTile tileFront2 = map.getTile(posFront2); + + if (tileFront2 == null || tileFront2.canPushBlocksInto(map, posFront2)) { + CoordI posFrontUp = coord.add(move).add_ip(0, 0, 1); // above front + MapTile tileFrontUp = map.getTile(posFrontUp); + if (tileFront.canBePushed(map, posFront) && tileFrontUp == null) { + // return true, if there is not a GOAL mark on top of this tile + return !map.getGoal().equals(posFrontUp); + + } else { + return false; + } + } + + return false; + } + + + /** + * Get if this entity can push blocks + * + * @return can actively push blocks + */ + public abstract boolean canPushBlocks(); + + + /** + * Get a deep copy + * + * @return copy + */ + public Entity copy() + { + try { + return getClass().newInstance().copyFrom(this); + } catch (Exception e) { + Log.e(e); + return null; + } + } + + + /** + * Fill this entity with data from other, copied entity. + * + * @param copied + * @return this + */ + public Entity copyFrom(Entity copied) + { + dead = copied.dead; + direction = copied.direction; + pos = copied.pos.copy(); + + inMotion = copied.inMotion; + moveProgress = copied.moveProgress.copy(); + moveDir = copied.moveDir; + + inRotation = copied.inRotation; + rotateProgress = copied.rotateProgress.copy(); + rotateDir = copied.rotateDir; + return this; + } + + + /** + * Get anim time for fall + * + * @param steps steps already fallen (for acceleration) + * @return time (s) + */ + public double getAnimTimeFall(int steps) + { + double a = 300; + double s = (1 + steps) * 10; + double t0 = Math.sqrt(2 * a * (s)); + double t1 = Math.sqrt(2 * a * (s + 10)); + double time = (t1 - t0) / 140; + return time; + } + + + /** + * Get length of side-move animation + * + * @return time in secs + */ + public double getAnimTimeHorizontal() + { + return ANIM_TIME_HORIZONTAL; + } + + + /** + * Get length of rotate animation + * + * @return time in secs + */ + public double getAnimTimeRotate() + { + return ANIM_TIME_ROTATE; + } + + + /** + * Get length of side-move animation [SLIDING - ON ICE] + * + * @return time in secs + */ + public double getAnimTimeSlide() + { + return ANIM_TIME_SLIDE; + } + + + /** + * Get currect tile coord (COPY) + * + * @return coord + */ + public CoordI getCoord() + { + return pos.copy(); + } + + + /** + * Get coord at move target + * + * @return tile coord + */ + public CoordI getCoordMoveTarget() + { + CoordI old = pos.copy(); + if (!inMotion) return old; + + switch (moveDir) { + case NORTH: + return old.add(0, -1, 0); + case SOUTH: + return old.add(0, 1, 0); + case WEST: + return old.add(-1, 0, 0); + case EAST: + return old.add(1, 0, 0); + case DOWN: + return old.add(0, 0, -1); + case UP: + return old.add(0, 0, 1); + } + + return old; + } + + + private CoordI getCoordOriginShadow(TurtleMap map) + { + CoordI originShadow = getCoord(); + + boolean originShadowFound = false; + + // find tile to cast the origin shadow on + originShadow.z--; + for (; originShadow.z >= 0; originShadow.z--) { + MapTile tile = map.getTile(originShadow); + if (tile != null && tile.isShadowSurface(map, originShadow)) { + originShadowFound = true; + originShadow.z++; + break; + } + } + + return originShadowFound ? originShadow : null; + } + + + private CoordI getCoordTargetShadow(TurtleMap map) + { + CoordI targetShadow = getCoordMoveTarget(); + + boolean targetShadowFound = false; + + // find tile to cast the origin shadow on + targetShadow.z--; + for (; targetShadow.z >= 0; targetShadow.z--) { + MapTile tile = map.getTile(targetShadow); + if (tile != null && tile.isShadowSurface(map, targetShadow)) { + targetShadowFound = true; + targetShadow.z++; + break; + } + } + + return targetShadowFound ? targetShadow : null; + } + + + /** + * Get a draw rect (Rect) + * + * @param mapMin map min render coord + * @param texture texture used for size - is aligned bottom left + * @param addZ flag whether 32xZ should be aded - disable to get map bottom + * @return the rect + */ + protected Rect getDrawRect(Coord mapMin, TxQuad texture, boolean addZ) + { + CoordI renderPos = getRenderCoord(); + + int minX = (int) (mapMin.x + renderPos.x * 64); + int minY = (int) (mapMin.y + (map.getSize().y - 1) * 64 - renderPos.y * 64 + (addZ ? 1 : 0) * renderPos.z * 32); + + Rect drawRect = Rect.fromSize(minX, minY, texture.size.toCoordI()); + if (inMotion) { + switch (moveDir) { + case NORTH: + drawRect.add_ip(0, 64D * moveProgress.delta()); + break; + + case SOUTH: + drawRect.add_ip(0, 64D * (1 - moveProgress.delta())); + break; + + case EAST: + drawRect.add_ip(-64D * (1D - moveProgress.delta()), 0); + break; + + case WEST: + drawRect.add_ip(-64D * moveProgress.delta(), 0); + break; + + case UP: + if (addZ) drawRect.add_ip(0, 32D * moveProgress.delta()); + break; + + case DOWN: + if (addZ) drawRect.add_ip(0, 32D * (1 - moveProgress.delta())); + break; + } + } + + return drawRect; + } + + + /** + * Get alpha for sprite + * + * @return alpha + */ + public double getAlphaSprite() + { + return isCarried ? 0.6 : 1; + } + + + /** + * Get alpha for shadow + * + * @return alpha + */ + public double getAlphaShadow() + { + return getAlphaSprite(); + } + + + /** + * Get coord for render order (which tile this entity should be rendered at) + * + * @return tile coord + */ + public CoordI getRenderCoord() + { + CoordI old = pos.copy(); + if (!inMotion) return old; + + switch (moveDir) { + case NORTH: + return old; + case SOUTH: + return old.add(0, 1, 0); + case WEST: + return old; + case EAST: + return old.add(1, 0, 0); + case DOWN: + return old.add(0, 0, -1); + case UP: + return old; + } + + return old; + } + + + /** + * Get texture quad of the current sprite frame
+ * Texture width must be 64, will be aligned to tile left bottom corner: + * 64x64 tile, 64x96 block + * + * @return texture quad + */ + public abstract TxQuad getSpriteFrame(); + + + /** + * Get shadow render offset (y negative = down) + * + * @return offset + */ + public Coord getSpriteOffset() + { + return new Coord(0, 0); + } + + + /** + * Get texture quad of the shadow of the current sprite frame
+ * Texture width must be 64, does not have to be the same size as Sprite + * Frame + * + * @return texture quad of the shadow + */ + public abstract TxQuad getSpriteShadowFrame(); + + + /** + * Get tile behind this entity + * + * @return tile behind + */ + public MapTile getTileBack() + { + return map.getTile(getCoord().sub_ip(direction.getMoveVector())); + } + + + /** + * Get tile under this entity + * + * @return tile under + */ + public MapTile getTileDown() + { + return map.getTile(getCoord().add_ip(0, 0, -1)); + } + + + /** + * Get tile in front of this entity + * + * @return tile forward + */ + public MapTile getTileFront() + { + return map.getTile(getCoord().add_ip(direction.getMoveVector())); + } + + + /** + * Get tile front down + * + * @return tile front down + */ + public MapTile getTileFrontDown() + { + return map.getTile(getCoord().add_ip(0, 0, -1).add_ip(direction.getMoveVector())); + } + + + /** + * Animate movement to direction - if last animation is finished and + * movement is legal + * + * @param dir move dir + */ + public void goToDir(MoveDir dir) + { + if (!canGoToDir(dir)) { + Log.w("Can't go to dir " + dir + " at " + Calc.cname(this)); + return; + } + + if (!isMoveFinished()) { + Log.w("Entity " + Calc.cname(this) + " is still in motion, can't start another move."); + } else { + changeMoveDir(dir); + + initMoveTriggers(); + + if (dir.isVertical()) { + stepsFalling++; + animateFall(); + changeMoveDir(MoveDir.DOWN); + inMotion = true; + } else { + CoordI front = getCoord(); + front.add_ip(dir.getMoveVector()); + MapTile tileFront = map.getTile(front); + boolean pushing = false; + if (tileFront != null && canPushBlocks() && !isSliding && tileFront.canBePushed(map, front) && tileFront.isSolidForCollision(map, front)) { + // start pushing + map.setTile(front, null); + EntityBlock e = new EntityBlock(tileFront); + e.setPos(front); + map.addEntity(e); + e.goToDir(dir); + e.onMoveStarted(); + pushing = true; + + } + + CoordI frontDown = getCoord().add(dir.getMoveVector()).add_ip(0, 0, -1); + MapTile tileFrontDown = map.getTile(frontDown); + //MapTile down = getTileDown(); + if (tileFrontDown != null && tileFrontDown.isSlipperly(map, frontDown) && !pushing) { + isSliding = true; + animateSlide(); + } else { + isSliding = false; + animateHorizontal(); + } + } + inMotion = true; + onMoveStarted(); + } + } + + + /** + * Get if shadow should be rendered + * + * @return has shadow + */ + public abstract boolean hasShadow(); + + + /** + * Change move direction, store old dir in moveDirLast if different from + * new. + * + * @param newMoveDir new dir + */ + public void changeMoveDir(MoveDir newMoveDir) + { + if (newMoveDir != moveDir) { + moveDirLast = moveDir; + } + moveDir = newMoveDir; + } + + + /** + * Get if this entity is dead + * + * @return is dead + */ + public boolean isDead() + { + return dead; + } + + + /** + * Get if entity movement is finished + * + * @return is finished + */ + public boolean isMoveFinished() + { + return !inMotion && !inRotation; + } + + + /** + * Called when a "movement trigger" was reached + * + * @param i number of trigger passed + */ + @Unimplemented + public void onMoveTrigger(int i) + {} + + + /** + * Try to slide on ice + * + * @return is sliding + */ + public boolean onStopMove_tryToSlide() + { + CoordI downPos = getCoord().add(0, 0, -1); + MapTile down = getTileDown(); + + isSliding = true; // set, because of checks in canGoToDir + if (down.isSlipperly(map, downPos) && !moveDir.isVertical() && canGoToDir(moveDir)) { + animateSlide(); + inMotion = true; + + initMoveTriggers(); + + onMoveStarted(); + return true; + } else { + isSliding = false; + return false; + } + } + + + /** + * Try to fall! + */ + public void tryToFall() + { + if (inMotion || isCarried) return; + onStopMove_tryToFall(); + } + + + /** + * Check if the turtle should fall, start falling if needed. + * + * @return is falling or was in motion + */ + public boolean onStopMove_tryToFall() + { + if (inMotion) return true; + + CoordI downPos = getCoord().add(0, 0, -1); + MapTile down = getTileDown(); + if (down == null || !down.isSolidForCollision(map, downPos)) { + goToDir(MoveDir.DOWN); + return true; + } else { + stepsFalling = 0; + return false; + } + } + + + /** + * Check if the turtle is out of map, set death if true. + * + * @return is now dead + */ + public boolean onStopMove_checkDieFall() + { + if (getCoord().z < -10) { + Log.f3("Entity '" + Calc.cname(this) + "' died."); + setDead(); + return true; + } + return false; + } + + + /** + * Called when movement animation is finished + */ + public void onStopMove() + { + isSliding = false; + + if (onStopMove_checkDieFall()) return; + if (isCarried) return; + + if (onStopMove_tryToFall()) return; + + onStopMove_tryToSlide(); + } + + + /** + * Called when rotation animation is finished + */ + public void onStopRotation() + { + tryToFall(); + } + + + /** + * Render the entity + * + * @param mapMin min map render coord + */ + public void render(Coord mapMin) + { + if (isDead()) return; + + // current sprite tile + if (hasShadow()) renderShadow(mapMin); + + renderSprite(mapMin); + } + + + @SuppressWarnings("null") + // wrong detect + private final void renderShadow(Coord mapMin) + { + RGB color = new RGB(RGB.WHITE, getAlphaCalculated(getAlphaShadow())); + + TxQuad shadow = getSpriteShadowFrame(); + if (shadow.size.xi() != 64 || shadow.size.yi() != 64) { + Log.w("Shadow texture should be 64x64 px in " + Calc.cname(this)); + } + + Rect drawRect = getDrawRect(mapMin, shadow, false); + + //Coord min = new Coord(, y) + Rect shadowRect = Rect.fromSize(drawRect.getMin(), shadow.size); + + // rendering part of the shadow which is on the last solid tile + CoordI posStart = getCoordOriginShadow(map); + CoordI posEnd = getCoordTargetShadow(map); + + boolean hasStart = (posStart != null); + boolean hasEnd = (posEnd != null); + + // if immediate mode + if (useImmediateShadow) { + drawRect = getDrawRect(mapMin, shadow, true); + shadowRect = Rect.fromSize(drawRect.getMin(), shadow.size); + RenderUtils.quadTextured(shadowRect, shadow, color); + return; + } + + // if static or shadows the same + if (!inMotion || (hasStart && hasEnd && posStart.z == posEnd.z)) { + if (hasStart) { + RenderUtils.quadTextured(shadowRect.add(0, (posStart.z) * 32), shadow, color); + } else if (hasEnd) { + RenderUtils.quadTextured(shadowRect.add(0, (posEnd.z) * 32), shadow, color); + } + + return; + } + + // ORIGIN SHADOW + do { + if (hasStart) { + boolean southUp = false; + boolean southDown = false; + if (hasEnd && moveDir == MoveDir.SOUTH) { + if (posEnd.z - posStart.z > 1) break; // double step, up = no shadow visible. + southUp = posEnd.z - posStart.z > 0; + } + + if (hasEnd && moveDir == MoveDir.SOUTH) { + southDown = posStart.z - posEnd.z > 0; + } + + if (!hasEnd && moveDir == MoveDir.SOUTH) { + southDown = true; // off the map + } + + CoordI originPos = getCoord(); + int minX2 = (int) (mapMin.x + originPos.x * 64); + int minY2 = (int) (mapMin.y + (map.getSize().y - 1) * 64 - originPos.y * 64); + + Rect rect = Rect.fromSize(new Coord(minX2, minY2), 64, 64).add(0, (posStart.z) * 32); + TxQuad tx = shadow.copy(); + + boolean elevatorFix = false; + double cut = (64) * moveProgress.delta(); + if (map.getTile(originPos.sub(0, 0, 1)) instanceof TileElevator) { +// if(moveDir == MoveDir.NORTH) { +// rect.growUp_ip(-8); +// tx.size.y -= 8; +// tx.uvs.growUp_ip(-8); +// } + if (moveDir == MoveDir.SOUTH) { + rect.growDown_ip(-4); + tx.size.y -= 4; + tx.uvs.growUp_ip(-4); + } else if (moveDir == MoveDir.NORTH) { + rect.growUp_ip(-8); + tx.size.y -= 8; + tx.uvs.growDown_ip(-8); + elevatorFix = true; + } else if (moveDir == MoveDir.EAST) { + rect.growRight_ip(-8); + tx.size.x -= 8; + tx.uvs.growRight_ip(-8); + elevatorFix = true; + } else if (moveDir == MoveDir.WEST) { + rect.growLeft_ip(-8); + tx.size.x -= 8; + tx.uvs.growLeft_ip(-8); + elevatorFix = true; + } + + // TODO east west + } + switch (moveDir) { + case NORTH: + rect.growDown_ip(-cut); + tx.size.y -= cut; + tx.uvs.growDown_ip(-cut); + break; + + case SOUTH: + rect.growUp_ip(-cut); + if (southUp) rect.growDown_ip(-32); + tx.size.y -= cut + (southUp ? 32 : 0); + tx.uvs.growUp_ip(-(cut + (southUp ? 32 : 0))); + break; + + case EAST: + rect.growLeft_ip(-cut); + tx.size.x -= cut; + tx.uvs.growRight_ip(-cut); + break; + + case WEST: + rect.growRight_ip(-cut); + tx.size.x -= cut; + tx.uvs.growLeft_ip(-cut); + break; + + } + + if (southUp && cut + 32 >= 64) { + // dont render + } else if (elevatorFix && cut + 8 >= 64) { + // dont render + } else { + RenderUtils.quadTextured(rect, tx, color); + + if (southDown && map.getTile(originPos.sub(0, 0, 1)).isShadowSide(map, originPos)) { + // render the side + tx.size.y = 1; + tx.uvs.growDown_ip(-tx.uvs.getSize().y - 1); + + rect = Rect.fromSize(new Coord(minX2, minY2), 64, 0).add(0, (posStart.z) * 32); + rect.growDown_ip((posStart.z - (hasEnd ? posEnd.z : 0)) * 32); + + RenderUtils.quadTextured(rect, tx, color); + } + } + } + } while (false); + + // TARGET SHADOW + if (hasEnd) { + boolean northDown = false; + boolean northUp = false; + if (hasStart && moveDir == MoveDir.NORTH) { + if (posStart.z - posEnd.z > 1) return; // double step = no shadow visible. + northDown = posEnd.z - posStart.z == -1; + } + + if (hasStart && moveDir == MoveDir.NORTH) { + northUp = posEnd.z - posStart.z > 0; + } + + if (!hasStart && moveDir == MoveDir.NORTH) { + northUp = true; + } + + CoordI targetPos = getCoordMoveTarget(); + int minX2 = (int) (mapMin.x + targetPos.x * 64); + int minY2 = (int) (mapMin.y + (map.getSize().y - 1) * 64 - targetPos.y * 64); + + Rect rect = Rect.fromSize(new Coord(minX2, minY2), 64, 64).add(0, (posEnd.z) * 32); + TxQuad tx = shadow.copy(); + + double cut = 64 * (1 - moveProgress.delta()); + + boolean elevatorFix = false; + if (map.getTile(getCoord().sub(0, 0, 1)) instanceof TileElevator) { + if (moveDir == MoveDir.SOUTH) { + rect.growUp_ip(8); + tx.size.y += 8; + tx.uvs.growDown_ip(8); + } + + if (moveDir == MoveDir.NORTH) { + rect.growDown_ip(8); + tx.size.y += 8; + tx.uvs.growUp_ip(8); + elevatorFix = true; + } + + if (moveDir == MoveDir.EAST) { + rect.growLeft_ip(8); + tx.size.y += 8; + tx.uvs.growLeft_ip(8); + elevatorFix = true; + } + + if (moveDir == MoveDir.WEST) { + rect.growRight_ip(8); + tx.size.y += 8; + tx.uvs.growRight_ip(8); + elevatorFix = true; + } + } + switch (moveDir) { + case NORTH: + rect.growUp_ip(-cut); + if (northDown) rect.growDown_ip(-32); + tx.size.y -= cut + (northDown ? 32 : 0); + tx.uvs.growUp_ip(-(cut + (northDown ? 32 : 0))); + break; + + case SOUTH: + rect.growDown_ip(-cut); + tx.size.y -= cut; + tx.uvs.growDown_ip(-cut); + break; + + case WEST: + rect.growLeft_ip(-cut); + tx.size.x -= cut; + tx.uvs.growRight_ip(-cut); + break; + + case EAST: + rect.growRight_ip(-cut); + tx.size.x -= cut; + tx.uvs.growLeft_ip(-cut); + break; + + } + + if (rect.getSize().x > 64) { + // prevent "repeating" + tx = shadow.copy(); + + if (moveDir == MoveDir.WEST) { + rect.growRight_ip(64 - rect.getSize().x); + } + + if (moveDir == MoveDir.EAST) { + rect.growLeft_ip(64 - rect.getSize().x); + } + + } + + if (elevatorFix && (cut + 8 >= 64)) { + // dont render + } else { + RenderUtils.quadTextured(rect, tx, color); + + if (northUp && map.getTile(targetPos.sub(0, 0, 1)).isShadowSurface(map, targetPos)) { + // render the side + tx.size.y = 1; + tx.uvs.growDown_ip(-tx.uvs.getSize().y - 1); + + rect = Rect.fromSize(new Coord(minX2, minY2), 64, 0).add(0, (posEnd.z) * 32); + rect.growDown_ip((posEnd.z - (hasStart ? posStart.z : 0)) * 32); + + RenderUtils.quadTextured(rect, tx, color); + } + + } + } + } + + + /** + * Render the entity sprite (no shadows) + * + * @param mapMin map min coord + */ + public void renderSprite(Coord mapMin) + { + TxQuad texture = getSpriteFrame(); + if (texture.size.xi() != 64) { + Log.w("Sprite texture should be 64px wide in " + getClass().getSimpleName()); + } + + Rect drawRect = getDrawRect(mapMin, texture, true); + + RGB color = new RGB(RGB.WHITE, getAlphaCalculated(getAlphaSprite())); + RenderUtils.quadTextured(drawRect.add_ip(getSpriteOffset()), texture, color); + } + + + private double getAlphaCalculated(double alpha) + { + if (getCoord().z <= 0) { + double down = (inMotion && moveDir == MoveDir.DOWN) ? moveProgress.delta() : 0; + double up = (inMotion && moveDir == MoveDir.UP) ? moveProgress.delta() : 0; + return Calc.clampd((1 - (-pos.z + down - up) * 0.15D) * alpha, 0, 1); + } + + double roof = map.getSize().z + 3; + if (getCoord().z >= roof) { + double down = (inMotion && moveDir == MoveDir.DOWN) ? moveProgress.delta() : 0; + double up = (inMotion && moveDir == MoveDir.UP) ? moveProgress.delta() : 0; + return Calc.clampd((1 - (pos.z - roof - down + up) * 0.5D) * alpha, 0, 1); + } + + return alpha; + } + + + /** + * Kill entity + */ + public final void setDead() + { + dead = true; + } + + + /** + * Set a turtle map this entity is in + * + * @param map the map + */ + public final void setMap(TurtleMap map) + { + this.map = map; + } + + + /** + * Set entity position + * + * @param thePos new pos + * @return this + */ + public Entity setPos(CoordI thePos) + { + pos.setTo(thePos); + return this; + } + + + /** + * Start animating right turn (CW) + */ + public void turnLeft() + { + if (!isMoveFinished()) { + Log.w("Entity " + Calc.cname(this) + " is still in motion, can't start another move."); + } else { + animateRotate(); + rotateDir = RotateDir.CCW; + inRotation = true; + onRotationStarted(); + } + } + + + /** + * Start animating left turn (CCW) + */ + public void turnRight() + { + if (!isMoveFinished()) { + Log.w("Entity " + Calc.cname(this) + " is still in motion, can't start another move."); + } else { + animateRotate(); + rotateDir = RotateDir.CW; + inRotation = true; + onRotationStarted(); + } + } + + + /** + * Update movement animations + * + * @param delta delta time + */ + public void updatePos(double delta) + { + moveProgress.update(delta); + rotateProgress.update(delta); + + // apply movement if finished + if (inMotion && moveProgress.isFinished()) { + // remember left tile + CoordI posLast = pos.copy(); + MapTile tileLast = map.getTile(posLast); + + CoordI posLastUnder = pos.add(0, 0, -1); + MapTile tileLastUnder = map.getTile(posLastUnder); + + // add movement increment to pos + pos.add_ip(moveDir.getMoveVector()); + inMotion = false; + + // get entered tiles + CoordI posNew = pos.copy(); + MapTile tileNew = map.getTile(posNew); + + CoordI posNewUnder = pos.add(0, 0, -1); + MapTile tileNewUnder = map.getTile(posNewUnder); + + // call tile triggers + if (tileLast != null) tileLast.onEntityLeave(map, posLast, this); + if (tileLastUnder != null) tileLastUnder.onEntityStepOff(map, posLastUnder, this); + + if (tileNew != null) tileNew.onEntityEnter(map, posNew, this); + if (tileNewUnder != null) tileNewUnder.onEntityStepOn(map, posNewUnder, this); + + onStopMove(); + } + + // apply rotation if finished + if (inRotation && rotateProgress.isFinished()) { + direction = direction.turn(rotateDir); + + inRotation = false; + onStopRotation(); + } + + double progress = moveProgress.delta(); + for (int i = 0; i < moveTriggers.length; i++) { + if (progress >= moveTriggers[i]) { + if (moveTriggersPassed[i] == false) { + onMoveTrigger(i); + moveTriggersPassed[i] = true; + } + } + } + } + + + /** + * Hook called when new move was just started + */ + @Unimplemented + public void onMoveStarted() + {} + + + /** + * Hook called when rotation was started + */ + @Unimplemented + public void onRotationStarted() + {} + + + /** + * Get trigger points for movement (0 to 1) + * + * @return array of triggers + */ + @Unimplemented + public double[] getMoveTriggerPoints() + { + return null; + } + + + /** + * Clear flags used for move triggers + */ + public void initMoveTriggers() + { + double[] triggers = getMoveTriggerPoints(); + + if (triggers == null) { + moveTriggers = new double[0]; + moveTriggersPassed = new boolean[0]; + } else { + moveTriggers = triggers; + moveTriggersPassed = new boolean[triggers.length]; + } + + } + +} diff --git a/src/net/tortuga/level/map/entities/EntityDescriptor.java b/src/net/tortuga/level/map/entities/EntityDescriptor.java new file mode 100644 index 0000000..d970aff --- /dev/null +++ b/src/net/tortuga/level/map/entities/EntityDescriptor.java @@ -0,0 +1,114 @@ +package net.tortuga.level.map.entities; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.tortuga.CustomIonMarks; +import net.tortuga.util.IonCoordI; + +import com.porcupine.coord.CoordI; +import com.porcupine.ion.Ion; +import com.porcupine.ion.Ionizable; + + +/** + * Entity descriptor for loading entities from ION + * + * @author MightyPork + */ +public class EntityDescriptor implements Ionizable { + + private int id; + private IonCoordI pos; + + + /** + * Entity descriptor - empty constructor + */ + public EntityDescriptor() {} + + + /** + * Entity descriptor + * + * @param id entity ID + * @param pos Coord + */ + public EntityDescriptor(int id, CoordI pos) { + this.id = id; + this.pos = new IonCoordI(pos); + } + + + /** + * Entity descriptor + * + * @param id entity ID + * @param x X + * @param y Y + * @param z Z + */ + public EntityDescriptor(int id, int x, int y, int z) { + this.id = id; + this.pos = new IonCoordI(x, y, z); + } + + + /** + * Get entity ID + * + * @return entity ID + */ + public int getId() + { + return id; + } + + + /** + * Get entity pos + * + * @return pos + */ + public CoordI getPos() + { + return pos; + } + + + /** + * Get entity at the given pos. + * + * @return the entity + */ + public Entity getEntity() + { + return EntityRegistry.getEntity(id).setPos(getPos()); + } + + + @Override + public void ionRead(InputStream in) throws IOException + { + id = (Integer) Ion.fromStream(in); + pos = (IonCoordI) Ion.fromStream(in); + } + + + @Override + public void ionWrite(OutputStream out) throws IOException + { + Ion.toStream(out, id); + Ion.toStream(out, pos); + } + + + @Override + public byte ionMark() + { + return CustomIonMarks.ENTITY_DESCRIPTOR; + } + +} diff --git a/src/net/tortuga/level/map/entities/EntityRegistry.java b/src/net/tortuga/level/map/entities/EntityRegistry.java new file mode 100644 index 0000000..17727fa --- /dev/null +++ b/src/net/tortuga/level/map/entities/EntityRegistry.java @@ -0,0 +1,94 @@ +package net.tortuga.level.map.entities; + + +import java.util.LinkedHashMap; + +import net.tortuga.level.map.entities.food.*; +import net.tortuga.util.Log; + + +/** + * Entity registry + * + * @author MightyPork + */ +public class EntityRegistry { + + private static LinkedHashMap> map = new LinkedHashMap>(); + + + /** + * Init entity registry + */ + public static void init() + { + register(1, FoodAppleGreen.class); + register(2, FoodAppleLime.class); + register(3, FoodAppleOrange.class); + register(4, FoodAppleRed.class); + register(5, FoodAppleYellow.class); + register(6, FoodPearOrange.class); + register(7, FoodPearYellow.class); + register(8, FoodCarrot.class); + register(9, FoodLettuce.class); + register(10, FoodTomatoPlain.class); + register(11, FoodTomatoWavy.class); + } + + + /** + * Register an entity + * + * @param index index + * @param entityClass entity class + */ + public static void register(int index, Class entityClass) + { + if (map.containsKey(index)) { + Log.w("Registered entity " + entityClass.getSimpleName() + " replaces " + map.get(index).getSimpleName() + " at index " + index); + } + + if (map.containsValue(entityClass)) { + Log.w("Registering entity " + entityClass.getSimpleName() + " to index " + index + " as duplicate."); + } + + map.put(index, entityClass); + } + + + /** + * Convert meta entity to real entity + * + * @param entity checked tile + * @return output tile for Map + */ + private static Entity processMetaEntity(Entity entity) + { + if (entity instanceof MetaEntity) { + return ((MetaEntity) entity).createEntity(); + } + + return entity; + } + + + /** + * Get instance of entity with given index + * + * @param index entity index + * @return new entity instance + */ + public static Entity getEntity(int index) + { + if (index == 0) return null; + if (!map.containsKey(index)) { + Log.w("There is no entity registered at index " + index + "."); + } + try { + return processMetaEntity(map.get(index).newInstance()); + } catch (Exception e) { + Log.e("Could not instantiate entity at " + index + ": " + map.get(index).getSimpleName()); + return null; + } + } +} diff --git a/src/net/tortuga/level/map/entities/EntityTx.java b/src/net/tortuga/level/map/entities/EntityTx.java new file mode 100644 index 0000000..d51fad7 --- /dev/null +++ b/src/net/tortuga/level/map/entities/EntityTx.java @@ -0,0 +1,98 @@ +package net.tortuga.level.map.entities; + + +import net.tortuga.textures.Textures; +import net.tortuga.textures.TxQuad; + + +/** + * Textures for entities (food) + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class EntityTx { + + // apples + public static TxQuad APPLE_RED; + public static TxQuad APPLE_RED_SHADOW; + + public static TxQuad APPLE_GREEN; + public static TxQuad APPLE_GREEN_SHADOW; + + public static TxQuad APPLE_YELLOW; + public static TxQuad APPLE_YELLOW_SHADOW; + + public static TxQuad APPLE_ORANGE; + public static TxQuad APPLE_ORANGE_SHADOW; + + public static TxQuad APPLE_LIME; + public static TxQuad APPLE_LIME_SHADOW; + + // pears + public static TxQuad PEAR_YELLOW; + public static TxQuad PEAR_YELLOW_SHADOW; + + public static TxQuad PEAR_ORANGE; + public static TxQuad PEAR_ORANGE_SHADOW; + + // carrot + public static TxQuad CARROT; + public static TxQuad CARROT_SHADOW; + + // lettuce + public static TxQuad LETTUCE; + public static TxQuad LETTUCE_SHADOW; + + // plain tomato + public static TxQuad TOMATO_PLAIN; + public static TxQuad TOMATO_PLAIN_SHADOW; + + // wavy tomato + public static TxQuad TOMATO_WAVY; + public static TxQuad TOMATO_WAVY_SHADOW; + + + /** + * Load + */ + public static void init() + { + // tiles: row1 + APPLE_RED = TxQuad.fromSize(Textures.food, 0, 0, 64, 64); + APPLE_RED_SHADOW = TxQuad.fromSize(Textures.food_shadows, 0, 0, 64, 64); + + APPLE_GREEN = TxQuad.fromSize(Textures.food, 64, 0, 64, 64); + APPLE_GREEN_SHADOW = TxQuad.fromSize(Textures.food_shadows, 64, 0, 64, 64); + + APPLE_YELLOW = TxQuad.fromSize(Textures.food, 64 * 2, 0, 64, 64); + APPLE_YELLOW_SHADOW = TxQuad.fromSize(Textures.food_shadows, 64 * 2, 0, 64, 64); + + APPLE_ORANGE = TxQuad.fromSize(Textures.food, 64 * 3, 0, 64, 64); + APPLE_ORANGE_SHADOW = TxQuad.fromSize(Textures.food_shadows, 64 * 3, 0, 64, 64); + + // 2nd row + APPLE_LIME = TxQuad.fromSize(Textures.food, 0, 64, 64, 64); + APPLE_LIME_SHADOW = TxQuad.fromSize(Textures.food_shadows, 0, 64, 64, 64); + + PEAR_YELLOW = TxQuad.fromSize(Textures.food, 64, 64, 64, 64); + PEAR_YELLOW_SHADOW = TxQuad.fromSize(Textures.food_shadows, 64, 64, 64, 64); + + PEAR_ORANGE = TxQuad.fromSize(Textures.food, 64 * 2, 64, 64, 64); + PEAR_ORANGE_SHADOW = TxQuad.fromSize(Textures.food_shadows, 64 * 2, 64, 64, 64); + + CARROT = TxQuad.fromSize(Textures.food, 64 * 3, 64, 64, 64); + CARROT_SHADOW = TxQuad.fromSize(Textures.food_shadows, 64 * 3, 64, 64, 64); + + // 3rd row + LETTUCE = TxQuad.fromSize(Textures.food, 0, 64 * 2, 64, 64); + LETTUCE_SHADOW = TxQuad.fromSize(Textures.food_shadows, 0, 64 * 2, 64, 64); + + TOMATO_PLAIN = TxQuad.fromSize(Textures.food, 64, 64 * 2, 64, 64); + TOMATO_PLAIN_SHADOW = TxQuad.fromSize(Textures.food_shadows, 64, 64 * 2, 64, 64); + + TOMATO_WAVY = TxQuad.fromSize(Textures.food, 64 * 2, 64 * 2, 64, 64); + TOMATO_WAVY_SHADOW = TxQuad.fromSize(Textures.food_shadows, 64 * 2, 64 * 2, 64, 64); + } + +} diff --git a/src/net/tortuga/level/map/entities/MetaEntity.java b/src/net/tortuga/level/map/entities/MetaEntity.java new file mode 100644 index 0000000..fc57761 --- /dev/null +++ b/src/net/tortuga/level/map/entities/MetaEntity.java @@ -0,0 +1,49 @@ +package net.tortuga.level.map.entities; + + +import net.tortuga.textures.TxQuad; + + +/** + * Meta tile used to build map tiles with special configurations. Is used in + * MapTileRegistry with separate ID. + * + * @author MightyPork + */ +public abstract class MetaEntity extends Entity { + + /** + * Create the right map tile + * + * @return the wrapped tile + */ + public abstract Entity createEntity(); + + + @Override + public boolean canPushBlocks() + { + return false; + } + + + @Override + public TxQuad getSpriteFrame() + { + return null; + } + + + @Override + public TxQuad getSpriteShadowFrame() + { + return null; + } + + + @Override + public boolean hasShadow() + { + return false; + } +} diff --git a/src/net/tortuga/level/map/entities/MoveDir.java b/src/net/tortuga/level/map/entities/MoveDir.java new file mode 100644 index 0000000..db2d159 --- /dev/null +++ b/src/net/tortuga/level/map/entities/MoveDir.java @@ -0,0 +1,157 @@ +package net.tortuga.level.map.entities; + + +import com.porcupine.coord.CoordI; + + +/** + * Direction of movement + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +// irrelevant +public enum MoveDir +{ + NORTH, SOUTH, EAST, WEST, UP, DOWN; + + /** + * Get the opposite direction + * + * @return opposite direction + */ + public MoveDir back() + { + switch (this) { + case NORTH: + return SOUTH; + case SOUTH: + return NORTH; + case EAST: + return WEST; + case WEST: + return EAST; + case UP: + return DOWN; + case DOWN: + return UP; + } + return null; + } + + + /** + * Get direction after rotation + * + * @param rotateDir direction of rotation + * @return processed direction + */ + public MoveDir turn(RotateDir rotateDir) + { + if (rotateDir == RotateDir.CW) { + switch (this) { + case NORTH: + return EAST; + case SOUTH: + return WEST; + case EAST: + return SOUTH; + case WEST: + return NORTH; + } + } else { + switch (this) { + case NORTH: + return WEST; + case SOUTH: + return EAST; + case EAST: + return NORTH; + case WEST: + return SOUTH; + } + } + return this; + } + + + /** + * Get rotation ordinal + * + * @return ordinal + */ + public int getRotateOrdinal() + { + switch (this) { + case NORTH: + return 1; + case SOUTH: + return 3; + case EAST: + return 0; + case WEST: + return 2; + } + + return 0; + } + + + /** + * Get movement vector + * + * @return vector + */ + public CoordI getMoveVector() + { + switch (this) { + case NORTH: + return new CoordI(0, -1, 0); + case SOUTH: + return new CoordI(0, 1, 0); + case EAST: + return new CoordI(1, 0, 0); + case WEST: + return new CoordI(-1, 0, 0); + case UP: + return new CoordI(0, 0, 1); + case DOWN: + return new CoordI(0, 0, -1); + } + + return new CoordI(0, 0, 0); + } + + + /** + * Get rotation index (frame number, (east) 0 to 127 (CCW), for spritesheet) + * + * @return index + */ + public int getRotationIndex() + { + switch (this) { + case NORTH: + return 32; + case SOUTH: + return 96; + case EAST: + return 0; + case WEST: + return 64; + } + + return 0; + } + + + /** + * Get if this movement is vertical = fall / fly + * + * @return is vertical + */ + public boolean isVertical() + { + return this == UP || this == DOWN; + } +} diff --git a/src/net/tortuga/level/map/entities/RotateDir.java b/src/net/tortuga/level/map/entities/RotateDir.java new file mode 100644 index 0000000..ecbfe42 --- /dev/null +++ b/src/net/tortuga/level/map/entities/RotateDir.java @@ -0,0 +1,17 @@ +package net.tortuga.level.map.entities; + + +/** + * Enum of rotation directions + * + * @author MightyPork + */ +public enum RotateDir +{ + /** Clockwise */ + CW, + + /** Counter-clockwise */ + CCW; + +} diff --git a/src/net/tortuga/level/map/entities/TurtleDescriptor.java b/src/net/tortuga/level/map/entities/TurtleDescriptor.java new file mode 100644 index 0000000..8bb3b0e --- /dev/null +++ b/src/net/tortuga/level/map/entities/TurtleDescriptor.java @@ -0,0 +1,106 @@ +package net.tortuga.level.map.entities; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.tortuga.CustomIonMarks; +import net.tortuga.util.IonCoordI; + +import com.porcupine.coord.CoordI; +import com.porcupine.ion.Ion; +import com.porcupine.ion.Ionizable; + + +/** + * Entity descriptor for loading entities from ION + * + * @author MightyPork + */ +public class TurtleDescriptor implements Ionizable { + + private int dir; + private IonCoordI pos; + + + /** + * Entity descriptor - empty constructor + */ + public TurtleDescriptor() {} + + + /** + * Turtle descriptor + * + * @param dir look direction: 0 east, 1 north, 2 west, 3 south + * @param pos pos + */ + public TurtleDescriptor(int dir, CoordI pos) { + this.dir = dir; + this.pos = new IonCoordI(pos); + } + + + /** + * Get entity pos + * + * @return pos + */ + public CoordI getPos() + { + return pos; + } + + +// /** +// * Get entity at the given pos. +// * @param theme turtle theme +// * @return the entity +// */ +// public EntityTurtle getEntity(ETurtle theme) { +// EntityTurtle e = new EntityTurtle(theme); +// e.setPos(getPos()); +// e.direction = getDir(); +// return e; +// } + + public MoveDir getDir() + { + switch (dir) { + case 0: + return MoveDir.EAST; + case 1: + return MoveDir.NORTH; + case 2: + return MoveDir.WEST; + case 3: + return MoveDir.SOUTH; + } + return MoveDir.EAST; + } + + + @Override + public void ionRead(InputStream in) throws IOException + { + dir = (Integer) Ion.fromStream(in); + pos = (IonCoordI) Ion.fromStream(in); + } + + + @Override + public void ionWrite(OutputStream out) throws IOException + { + Ion.toStream(out, dir); + Ion.toStream(out, pos); + } + + + @Override + public byte ionMark() + { + return CustomIonMarks.TURTLE_DESCRIPTOR; + } + +} diff --git a/src/net/tortuga/level/map/entities/food/EFood.java b/src/net/tortuga/level/map/entities/food/EFood.java new file mode 100644 index 0000000..b36172f --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/EFood.java @@ -0,0 +1,62 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.EntityTx; +import net.tortuga.textures.TxQuad; + + +/** + * Enum of food types, with sprite textures and shadows + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +// irrelevant +public enum EFood +{ + //@formatter:off + + APPLE_RED(EntityTx.APPLE_RED, EntityTx.APPLE_RED_SHADOW), + APPLE_ORANGE(EntityTx.APPLE_ORANGE, EntityTx.APPLE_ORANGE_SHADOW), + APPLE_YELLOW(EntityTx.APPLE_YELLOW, EntityTx.APPLE_YELLOW_SHADOW), + APPLE_LIME(EntityTx.APPLE_LIME, EntityTx.APPLE_LIME_SHADOW), + APPLE_GREEN(EntityTx.APPLE_GREEN, EntityTx.APPLE_GREEN_SHADOW), + PEAR_YELLOW(EntityTx.PEAR_YELLOW, EntityTx.PEAR_YELLOW_SHADOW), + PEAR_ORANGE(EntityTx.PEAR_ORANGE, EntityTx.PEAR_ORANGE_SHADOW), + CARROT(EntityTx.CARROT, EntityTx.CARROT_SHADOW), + LETTUCE(EntityTx.LETTUCE, EntityTx.LETTUCE_SHADOW), + TOMATO_PLAIN(EntityTx.TOMATO_PLAIN, EntityTx.TOMATO_PLAIN_SHADOW), + TOMATO_WAVY(EntityTx.TOMATO_WAVY, EntityTx.TOMATO_WAVY_SHADOW); + + //@formatter:on + + private TxQuad sprite, shadow; + + + private EFood(TxQuad sprite, TxQuad shadow) { + this.sprite = sprite; + this.shadow = shadow; + } + + + /** + * Get the sprite frame + * + * @return sprite frame + */ + public TxQuad getSpriteFrame() + { + return sprite; + } + + + /** + * Get the shadow frame + * + * @return shadow frame + */ + public TxQuad getShadowFrame() + { + return shadow; + } +} diff --git a/src/net/tortuga/level/map/entities/food/EntityFood.java b/src/net/tortuga/level/map/entities/food/EntityFood.java new file mode 100644 index 0000000..e39bc95 --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/EntityFood.java @@ -0,0 +1,150 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.sounds.Effects; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.AnimDouble; + +import com.porcupine.coord.Coord; + + +/** + * Food entity + * + * @author MightyPork + */ +public class EntityFood extends Entity { + + private AnimDouble initFadeAnim = new AnimDouble(0); + + private EFood type = EFood.APPLE_RED; + private double bounceOffset; + /** Fading progress */ + private AnimDouble fadeProgress = new AnimDouble(0); + private boolean willEat = false; + + + @Override + public void onAddedToMap() + { + super.onAddedToMap(); + initFadeAnim.animIn(1.2); + } + + + /** + * Animate food eating (fade out) + * + * @param time animation length + */ + public void animateEat(double time) + { + willEat = true; + fadeProgress.animIn(time); + + Effects.play("turtle.eat", 0.7 + 0.5 * rand.nextDouble(), 1); + } + + + /** + * Animate food destruction (fade out) + * + * @param time animation length + */ + public void animateDestroy(double time) + { + willEat = false; + fadeProgress.animIn(time); + + Effects.play("fruit.destroy", 0.7 + 0.5 * rand.nextDouble(), 1); + } + + + /** + * food type + * + * @param type food type + */ + public EntityFood(EFood type) { + this.type = type; + bounceOffset = rand.nextDouble() * 6.28D; + } + + + /** + * Moving block entity + */ + public EntityFood() {} + + + @Override + public void updatePos(double delta) + { + super.updatePos(delta); + + fadeProgress.update(delta); + + initFadeAnim.update(delta); + + if (fadeProgress.delta() == 1 && fadeProgress.isFinished()) { + if (willEat) map.controller.onFoodEaten(); + setDead(); + } + } + + + @Override + public boolean hasShadow() + { + return true; + } + + + @Override + public Coord getSpriteOffset() + { + double vert = 2 * Math.sin((System.currentTimeMillis() / 3000D) * 6.28D + bounceOffset); + return new Coord(0, 8 + (moveDir.isVertical() && inMotion ? 0 : 1) * vert); + } + + + @Override + public TxQuad getSpriteFrame() + { + return type.getSpriteFrame(); + } + + + @Override + public TxQuad getSpriteShadowFrame() + { + return type.getShadowFrame(); + } + + + @Override + public Entity copyFrom(Entity copied) + { + super.copyFrom(copied); + + type = ((EntityFood) copied).type; + bounceOffset = rand.nextDouble() * 6.28D; + + return this; + } + + + @Override + public boolean canPushBlocks() + { + return false; + } + + + @Override + public double getAlphaSprite() + { + return (1D - fadeProgress.delta()) * super.getAlphaSprite() * initFadeAnim.delta(); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodAppleGreen.java b/src/net/tortuga/level/map/entities/food/FoodAppleGreen.java new file mode 100644 index 0000000..752a819 --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodAppleGreen.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodAppleGreen extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.APPLE_GREEN); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodAppleLime.java b/src/net/tortuga/level/map/entities/food/FoodAppleLime.java new file mode 100644 index 0000000..42c412f --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodAppleLime.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodAppleLime extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.APPLE_LIME); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodAppleOrange.java b/src/net/tortuga/level/map/entities/food/FoodAppleOrange.java new file mode 100644 index 0000000..f80b5d8 --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodAppleOrange.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodAppleOrange extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.APPLE_ORANGE); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodAppleRed.java b/src/net/tortuga/level/map/entities/food/FoodAppleRed.java new file mode 100644 index 0000000..7894e9d --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodAppleRed.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodAppleRed extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.APPLE_RED); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodAppleYellow.java b/src/net/tortuga/level/map/entities/food/FoodAppleYellow.java new file mode 100644 index 0000000..a5a6253 --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodAppleYellow.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodAppleYellow extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.APPLE_YELLOW); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodCarrot.java b/src/net/tortuga/level/map/entities/food/FoodCarrot.java new file mode 100644 index 0000000..8727c83 --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodCarrot.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodCarrot extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.CARROT); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodLettuce.java b/src/net/tortuga/level/map/entities/food/FoodLettuce.java new file mode 100644 index 0000000..3a7c695 --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodLettuce.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodLettuce extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.LETTUCE); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodPearOrange.java b/src/net/tortuga/level/map/entities/food/FoodPearOrange.java new file mode 100644 index 0000000..b9ef6dc --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodPearOrange.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodPearOrange extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.PEAR_ORANGE); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodPearYellow.java b/src/net/tortuga/level/map/entities/food/FoodPearYellow.java new file mode 100644 index 0000000..7022654 --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodPearYellow.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodPearYellow extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.PEAR_YELLOW); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodTomatoPlain.java b/src/net/tortuga/level/map/entities/food/FoodTomatoPlain.java new file mode 100644 index 0000000..6057cd2 --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodTomatoPlain.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodTomatoPlain extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.TOMATO_PLAIN); + } +} diff --git a/src/net/tortuga/level/map/entities/food/FoodTomatoWavy.java b/src/net/tortuga/level/map/entities/food/FoodTomatoWavy.java new file mode 100644 index 0000000..19c71ca --- /dev/null +++ b/src/net/tortuga/level/map/entities/food/FoodTomatoWavy.java @@ -0,0 +1,15 @@ +package net.tortuga.level.map.entities.food; + + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MetaEntity; + + +public class FoodTomatoWavy extends MetaEntity { + + @Override + public Entity createEntity() + { + return new EntityFood(EFood.TOMATO_WAVY); + } +} diff --git a/src/net/tortuga/level/map/entities/mobile/ETurtle.java b/src/net/tortuga/level/map/entities/mobile/ETurtle.java new file mode 100644 index 0000000..dc0291a --- /dev/null +++ b/src/net/tortuga/level/map/entities/mobile/ETurtle.java @@ -0,0 +1,57 @@ +package net.tortuga.level.map.entities.mobile; + + +import net.tortuga.textures.Textures; + +import org.newdawn.slick.opengl.Texture; + + +/** + * Enum of turtle themes + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +// irrelevant +public enum ETurtle +{ + //@formatter:off + DEFAULT(Textures.turtle_brown_lt), + BROWN_LIGHT(Textures.turtle_brown_lt), + BROWN_DARK(Textures.turtle_brown_dk), + BROWN_ORNAMENTAL(Textures.turtle_brown_orn), + RED(Textures.turtle_red), + GREEN(Textures.turtle_green), + PURPLE(Textures.turtle_purple), + YELLOW(Textures.turtle_yellow), + BLUE(Textures.turtle_blue); + //@formatter:on + + private ETurtle(Texture tx) { + this.tx = tx; + } + + private Texture tx; + + + /** + * Get turtle sprite sheet texture + * + * @return spritesheet + */ + public Texture getSpriteTexture() + { + return tx; + } + + + /** + * Get shadow sprite sheet texture + * + * @return spritesheet + */ + public Texture getShadowTexture() + { + return Textures.turtle_shadows; + } +} diff --git a/src/net/tortuga/level/map/entities/mobile/EntityBlock.java b/src/net/tortuga/level/map/entities/mobile/EntityBlock.java new file mode 100644 index 0000000..ce9d37e --- /dev/null +++ b/src/net/tortuga/level/map/entities/mobile/EntityBlock.java @@ -0,0 +1,212 @@ +package net.tortuga.level.map.entities.mobile; + + +import java.util.Set; + +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MoveDir; +import net.tortuga.level.map.entities.food.EntityFood; +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.sounds.Effects; +import net.tortuga.sounds.Loops; +import net.tortuga.textures.Textures; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.AnimDouble; + +import com.porcupine.coord.Coord; + + +/** + * A block entity + * + * @author MightyPork + */ +public class EntityBlock extends Entity { + + /** The represented map tile */ + public MapTile tile; + private Entity carriedEntity; + + + /** + * Moving block + * + * @param tile tile + */ + public EntityBlock(MapTile tile) { + this.tile = tile; + } + + + /** + * Moving block entity + */ + public EntityBlock() {} + + + @Override + public void onStopMove() + { + super.onStopMove(); + + if (isDead()) { + Loops.stop("block.move", 0.2); + Effects.play("water.splash", 0.6 + 0.5 * rand.nextDouble(), 1); + return; + } + + if (moveDir.isVertical()) { + Loops.stop("block.move", 0.2); + } + + if (!inMotion) { + Loops.stop("block.move", 0.2); + + // solidify + map.setTile(getCoord(), tile); + setDead(); + + if (moveDir == MoveDir.DOWN) { + Effects.play("block.drop", 0.7 + 0.4 * rand.nextDouble(), 1); + } + } + + } + + + @Override + public boolean hasShadow() + { + return true; + } + + + @Override + public Coord getSpriteOffset() + { + return new Coord(0, 0); + } + + + @Override + public TxQuad getSpriteFrame() + { + return tile.getTexture(); + } + + + @Override + public TxQuad getSpriteShadowFrame() + { + return TxQuad.fromSize(Textures.turtle_shadows, 0, 12 * 64, 64, 64); + } + + + @Override + public Entity copyFrom(Entity copied) + { + super.copyFrom(copied); + + tile = ((EntityBlock) copied).tile; + + return this; + } + + + @Override + public boolean canPushBlocks() + { + return false; + } + + + @Override + public double getAlphaSprite() + { + return 0.6; + } + + + @Override + public double[] getMoveTriggerPoints() + { + return new double[] { 0.3 }; + } + + + @Override + public void onMoveTrigger(int i) + { + double time = moveProgress.time; + if (moveDir == MoveDir.DOWN) time *= 0.2; + if (moveDir == MoveDir.EAST) time *= 0.3; + if (moveDir == MoveDir.WEST) time *= 0.3; + if (moveDir == MoveDir.NORTH) time *= 0.2; + if (moveDir == MoveDir.SOUTH) time *= 0.2; + + Set entities = map.getEntitiesAtCoord(getCoordMoveTarget()); + for (Entity e : entities) { + if (e instanceof EntityFood) { + ((EntityFood) e).animateDestroy(time); + } + } + + } + + + @Override + public void onMoveStarted() + { + Loops.start("block.move", 0.2); + + // get entity to carry = food + Set entities = map.getEntitiesAtCoord(getCoord().add(0, 0, 1)); + for (Entity e : entities) { + if (e instanceof EntityFood) { + carriedEntity = e; + map.removeEntity(e); + e.moveDir = moveDir; + e.inMotion = true; + e.isCarried = true; + e.moveProgress.animIn(moveProgress.time); + e.isSliding = isSliding; + } + } + } + + + @Override + public void updatePos(double delta) + { + super.updatePos(delta); + + if (carriedEntity != null) { + carriedEntity.updatePos(delta); + + if (carriedEntity.isMoveFinished()) { + if (inMotion) { + carriedEntity.moveDir = moveDir; + carriedEntity.inMotion = true; + carriedEntity.isCarried = true; + carriedEntity.moveProgress.animIn(moveProgress.time); + carriedEntity.isSliding = isSliding; + } else { + map.addEntity(carriedEntity); + carriedEntity.isCarried = false; + carriedEntity.inMotion = false; + carriedEntity.moveProgress = new AnimDouble(0); + carriedEntity.isSliding = false; + } + + } + } + } + + + @Override + public void render(Coord mapMin) + { + super.render(mapMin); + if (carriedEntity != null) carriedEntity.renderSprite(mapMin); + } +} diff --git a/src/net/tortuga/level/map/entities/mobile/EntityTurtle.java b/src/net/tortuga/level/map/entities/mobile/EntityTurtle.java new file mode 100644 index 0000000..849d5f2 --- /dev/null +++ b/src/net/tortuga/level/map/entities/mobile/EntityTurtle.java @@ -0,0 +1,315 @@ +package net.tortuga.level.map.entities.mobile; + + +import java.util.Set; + +import net.tortuga.level.TurtleController; +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MoveDir; +import net.tortuga.level.map.entities.RotateDir; +import net.tortuga.level.map.entities.food.EntityFood; +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.mechanisms.TileSwitch; +import net.tortuga.sounds.Effects; +import net.tortuga.sounds.Loops; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.AnimDouble; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; + + +/** + * The turtle entity + * + * @author MightyPork + */ +public class EntityTurtle extends Entity { + + private AnimDouble initFadeAnim = new AnimDouble(0); + + /** Turtle controller */ + public TurtleController ctrl; + + /** Active spritesheet texture */ + public ETurtle theme; + + +// @Override +// public double getAlphaSprite() { +// return 1; +// } + +// @Override +// public double getAlphaShadow() { +// return 1; +// } + + @Override + public void onAddedToMap() + { + super.onAddedToMap(); + initFadeAnim.animIn(0.6); + } + + + @Override + public double getAlphaShadow() + { + return super.getAlphaShadow() * initFadeAnim.delta(); + } + + + @Override + public double getAlphaSprite() + { + return super.getAlphaSprite() * initFadeAnim.delta(); + } + + + @Override + public void updatePos(double delta) + { + super.updatePos(delta); + initFadeAnim.update(delta); + } + + + @Override + public void onStopMove() + { + // TODO check for winning conditions + + super.onStopMove(); + + if (isDead()) { + Loops.stop("turtle.move", 0.2); + Effects.play("water.splash", 0.6 + 0.5 * rand.nextDouble(), 1); + ctrl.onTurtleDied(); + return; + } + + // TODO ask controller for next step. + + if (!inMotion || moveDir.isVertical()) { + Loops.stop("turtle.move", 0.2); + } + + if (!inMotion && moveDir == MoveDir.DOWN) { + Effects.play("turtle.drop", 0.7 + 0.4 * rand.nextDouble(), 1); + } + } + + + @Override + public void onStopRotation() + { + super.onStopRotation(); + + if (!inRotation) { + Loops.stop("turtle.move", 0.2); + } + } + + + @Override + public void onRotationStarted() + { + Loops.start("turtle.move", 0.2); + } + + + @Override + public double[] getMoveTriggerPoints() + { + double dist = 0.5; + if (moveDir == MoveDir.DOWN) dist = 0.3; + if (moveDir == MoveDir.EAST) dist = 0.2; + if (moveDir == MoveDir.WEST) dist = 0.2; + if (moveDir == MoveDir.NORTH) dist = 0.3; + if (moveDir == MoveDir.SOUTH) dist = 0.3; + + return new double[] { dist, 0.5 }; + } + + + @Override + public void onMoveTrigger(int i) + { + switch (i) { + case 0: // food + double time = moveProgress.time; + if (moveDir == MoveDir.DOWN) time *= 0.2; + if (moveDir == MoveDir.EAST) time *= 0.3; + if (moveDir == MoveDir.WEST) time *= 0.3; + if (moveDir == MoveDir.NORTH) time *= 0.2; + if (moveDir == MoveDir.SOUTH) time *= 0.2; + + Set entities = map.getEntitiesAtCoord(getCoordMoveTarget()); + for (Entity e : entities) { + if (e instanceof EntityFood) { + ((EntityFood) e).animateEat(time); + } + } + break; + + case 1: // switches + CoordI targetPos = getCoordMoveTarget(); + MapTile tgTile = map.getTile(targetPos); + if (tgTile != null && tgTile instanceof TileSwitch) { + ((TileSwitch) tgTile).switchByTurtle(); + } + break; + } + } + + + /** + * Turtle entity + */ + public EntityTurtle() {} + + + /** + * Turtle entity + * + * @param theme + */ + public EntityTurtle(ETurtle theme) { + this.theme = theme; + } + + + @Override + public boolean canPushBlocks() + { + return true; + } + + + @Override + public Entity copyFrom(Entity copied) + { + super.copyFrom(copied); + + theme = ((EntityTurtle) copied).theme; + + return this; + } + + + @Override + public TxQuad getSpriteFrame() + { + CoordI index = getSpritesheetIndex(); + return TxQuad.fromSize(theme.getSpriteTexture(), index.x * 64, index.y * 64, 64, 64); + } + + + @Override + public Coord getSpriteOffset() + { + double vert = Math.sin((System.currentTimeMillis() / 1300D) * 6.28D); + return new Coord(0, 6 + (moveDir.isVertical() && inMotion ? 0 : 1) * vert); + } + + + @Override + public TxQuad getSpriteShadowFrame() + { + CoordI index = getSpritesheetIndex(); + return TxQuad.fromSize(theme.getShadowTexture(), index.x * 64, index.y * 64, 64, 64); + } + + + private CoordI getSpritesheetIndex() + { + // no motion or rotation - static turtle + if (!inMotion && !inRotation) { + return new CoordI(0, direction.getRotateOrdinal()); + } + + // motion + if (inMotion) { + if (moveDir.isVertical()) { + return new CoordI(0, direction.getRotateOrdinal()); + } else { + int moveIndex; + + if (!isSliding) { + moveIndex = (int) (Math.round((moveProgress.delta() * 32)) % 16); + } else { + moveIndex = (int) (Math.round((moveProgress.delta() * 16)) % 16); + } + + return new CoordI(moveIndex, direction.getRotateOrdinal()); + } + } + + // rotation + if (inRotation) { + int rotateIndex = direction.getRotationIndex(); + + if (rotateDir == RotateDir.CCW) { + rotateIndex += Math.round(rotateProgress.delta() * 32); + } + + if (rotateDir == RotateDir.CW) { + rotateIndex -= Math.round(rotateProgress.delta() * 32); + } + + if (rotateIndex < 0) rotateIndex += 128; + if (rotateIndex > 127) rotateIndex -= 128; + + return new CoordI(rotateIndex % 16, 4 + (int) Math.floor(rotateIndex / 16D)); + } + + return new CoordI(0, 0); + } + + + /** + * Go backward + */ + public void goBackward() + { + goToDir(direction.back()); + } + + + /** + * Go forward + */ + public void goForward() + { + goToDir(direction); + } + + + @Override + public boolean hasShadow() + { + return true; + } + + + /** + * Set theme. + * + * @param theme theme + */ + public void setTheme(ETurtle theme) + { + this.theme = theme; + } + + + @Override + public void onMoveStarted() + { + if (!moveDir.isVertical()) { + Loops.start("turtle.move", 0.2); + } + } + +} diff --git a/src/net/tortuga/level/map/tiles/MapTile.java b/src/net/tortuga/level/map/tiles/MapTile.java new file mode 100644 index 0000000..fdd38e6 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/MapTile.java @@ -0,0 +1,500 @@ +package net.tortuga.level.map.tiles; + + +import static org.lwjgl.opengl.GL11.*; + +import java.util.Random; + +import net.tortuga.annotations.Unimplemented; +import net.tortuga.level.map.TurtleMap; +import net.tortuga.level.map.entities.Entity; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.Log; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.coord.Rect; +import com.porcupine.math.Calc; + + +/** + * Base map tile + * + * @author MightyPork + */ +public abstract class MapTile { + + /** RNG */ + public static Random rand = new Random(); + + + /** + * Hook called when tile was added to map + * + * @param map + * @param tilePos + */ + @Unimplemented + public void onAddedToMap(TurtleMap map, CoordI tilePos) + {} + + + /** + * Get if tile can be picked up by turtle + * + * @param map the map + * @param tilePos tile pos + * @return can be picked up + */ + public boolean canBePickedUp(TurtleMap map, CoordI tilePos) + { + return false; + } + + + /** + * Get if tile can be pushed by turtle + * + * @param map the map + * @param tilePos tile pos + * @return can be pushed + */ + public boolean canBePushed(TurtleMap map, CoordI tilePos) + { + return false; + } + + + /** + * Get a copy + * + * @return copy + */ + public MapTile copy() + { + try { + return getClass().newInstance().copyFrom(this); + } catch (Exception e) { + Log.w("Could not copy " + Calc.cname(this)); + return null; + } + } + + + /** + * Copy parameters from another tile + * + * @param copied copied tile + * @return this + */ + public MapTile copyFrom(MapTile copied) + { + return this; + } + + + /** + * Get tile texture + * + * @return texture quad + */ + public abstract TxQuad getTexture(); + + + /** + * is ice? + * + * @param map the map + * @param tilePos tile pos + * @return is ice + */ + public boolean isSlipperly(TurtleMap map, CoordI tilePos) + { + return false; + } + + + /** + * Get if tile can be walked through + * + * @param map the map + * @param tilePos tile pos + * @return can be walked through + */ + public boolean isSolidForCollision(TurtleMap map, CoordI tilePos) + { + return true; + } + + + /** + * Get if tile uses cube render shading + * + * @param map the map + * @param tilePos tile pos + * @return can be walked through + */ + public boolean isShadedAsCube(TurtleMap map, CoordI tilePos) + { + return true; + } + + + /** + * Get if turtle can push blocks into this tile - used by elevator. + * + * @param map the map + * @param tilePos tile pos + * @return can push blocks into + */ + public boolean canPushBlocksInto(TurtleMap map, CoordI tilePos) + { + return false; + } + + + /** + * Get if tile can have shadow on top + * + * @param map the map + * @param tilePos tile pos + * @return can have shadow on top + */ + public boolean isShadowSurface(TurtleMap map, CoordI tilePos) + { + return true; + } + + + /** + * Get if tile can have shadow rendered on side + * + * @param map the map + * @param tilePos tile pos + * @return can have shadow on side + */ + public boolean isShadowSide(TurtleMap map, CoordI tilePos) + { + return true; + } + + + /** + * Get if this tile is transparent = ice - for culling + * + * @param map the map + * @param tilePos tile pos + * @return is transparent + */ + public boolean isGlassy(TurtleMap map, CoordI tilePos) + { + return false; + } + + + /** + * Called when entity walks into this tile's area + * + * @param map turtle map + * @param tilePos this tile pos + * @param entity the entity + */ + @Unimplemented + public void onEntityEnter(TurtleMap map, CoordI tilePos, Entity entity) + {} + + + /** + * Called when entity leaves this tile's area + * + * @param map turtle map + * @param tilePos this tile pos + * @param entity the entity + */ + @Unimplemented + public void onEntityLeave(TurtleMap map, CoordI tilePos, Entity entity) + {} + + + /** + * Called when entity steps onto this tile (is above) + * + * @param map turtle map + * @param tilePos this tile pos + * @param entity the entity + */ + @Unimplemented + public void onEntityStepOn(TurtleMap map, CoordI tilePos, Entity entity) + {} + + + /** + * Called when entity steps off this tile (was above, went away) + * + * @param map turtle map + * @param tilePos this tile pos + * @param entity the entity + */ + @Unimplemented + public void onEntityStepOff(TurtleMap map, CoordI tilePos, Entity entity) + {} + + + /** + * Perform animation update if needed + * + * @param map turtle map + * @param tilePos this tile pos + * @param delta delta time + */ + @Unimplemented + public void update(TurtleMap map, CoordI tilePos, double delta) + {} + + + /** + * Render tile + * + * @param mapMin map rect min + * @param tilePos tile position in map (counted from top left down) + * @param map the map + */ + public void render(Coord mapMin, CoordI tilePos, TurtleMap map) + { + //, tilePos.z*1+1 + Coord quadMin = mapMin.add(tilePos.x * 64, (map.getSize().y - 1) * 64 - tilePos.y * 64 + tilePos.z * 32); + Coord topMid = quadMin.add(32, 64); + Coord sideMid = quadMin.add(32, 16); + Coord lowFaceMid = quadMin.add(32, 32); + + // rects of face sizes + Rect topRect = new Rect(-32, -32, 32, 32); + Rect sideRect = new Rect(-32, -16, 32, 16); + Rect tileQuad = Rect.fromSize(quadMin, 64, 96); + Rect topLineQuad = tileQuad.getEdgeTop().add_ip(0, -3).growUp_ip(32); + + /* + * [topLeft] [top] [topRight] + * [leftUp] [tile] [rightUp] + * [downLeft] [down|Up|Down] [downRight] + * + */ + + // north + boolean iNW = map.isTileFullCube(tilePos.add(-1, -1, 0)); + boolean iN = map.isTileFullCube(tilePos.add(0, -1, 0)); + boolean iNE = map.isTileFullCube(tilePos.add(1, -1, 0)); + + boolean iNW_U = map.isTileFullCube(tilePos.add(-1, -1, 1)); + boolean iN_U = map.isTileFullCube(tilePos.add(0, -1, 1)); + boolean iNE_U = map.isTileFullCube(tilePos.add(1, -1, 1)); + + boolean iNW_D = map.isTileFullCube(tilePos.add(-1, -1, -1)); + boolean iN_D = map.isTileFullCube(tilePos.add(0, -1, -1)); + boolean iNE_D = map.isTileFullCube(tilePos.add(1, -1, -1)); + + boolean iW = map.isTileFullCube(tilePos.add(-1, 0, 0)); + boolean iE = map.isTileFullCube(tilePos.add(1, 0, 0)); + + boolean iW_U = map.isTileFullCube(tilePos.add(-1, 0, 1)); + boolean iU = map.isTileFullCube(tilePos.add(0, 0, 1)); + boolean iE_U = map.isTileFullCube(tilePos.add(1, 0, 1)); + + boolean iW_D = map.isTileFullCube(tilePos.add(-1, 0, -1)); + boolean iD = map.isTileFullCube(tilePos.add(0, 0, -1)); + boolean iE_D = map.isTileFullCube(tilePos.add(1, 0, -1)); + + boolean iSW = map.isTileFullCube(tilePos.add(-1, 1, 0)); + boolean iS = map.isTileFullCube(tilePos.add(0, 1, 0)); + boolean transpS = (iS && map.getTile(tilePos.add(0, 1, 0)).isGlassy(map, tilePos.add(0, 1, 0))); + boolean iSE = map.isTileFullCube(tilePos.add(1, 1, 0)); + + boolean iSW_U = map.isTileFullCube(tilePos.add(-1, 1, 1)); + boolean iS_U = map.isTileFullCube(tilePos.add(0, 1, 1)); + boolean iSE_U = map.isTileFullCube(tilePos.add(1, 1, 1)); + + boolean iSW_D = map.isTileFullCube(tilePos.add(-1, 1, -1)); + boolean iS_D = map.isTileFullCube(tilePos.add(0, 1, -1)); + boolean iSE_D = map.isTileFullCube(tilePos.add(1, 1, -1)); + + boolean isLowest = !iD; +// boolean isLowest = true; +// for (int z = tilePos.z - 1; z >= 0; z--) { +// if (map.isTileSolid(tilePos.x, tilePos.y, z)) { +// isLowest = false; +// } +// } + + /* + * Shadows + * abv + * +-------------+ + * |tLU tU tRU| + * | | + * |tL tR| + * | | + * |tLD -- tRD| + * +-------------+ + * | | + * |sL sR| + * | sD | + * +-------------+ + * + */ + + boolean tmpW = (iW || iNW); + boolean tmpE = (iE || iNE); + + boolean abvInner = !iU && !iN && (tmpW && tmpE); + boolean abvLeft = !iU && !iN && (!tmpW && tmpE); + boolean abvRight = !iU && !iN && (tmpW && !tmpE); + boolean abvBoth = !iU && !iN && (!tmpW && !tmpE); + + boolean tLU = !iU && iNW_U && (iW_U == iN_U); + boolean tU = !iU && iN_U; + boolean tRU = !iU && iNE_U && (iE_U == iN_U); + boolean tL = !iU && iW_U; + boolean tR = !iU && iE_U; + boolean tLD = !iU && iSW_U && (iW_U == iS_U); + boolean tRD = !iU && iSE_U && (iE_U == iS_U); + boolean sL = !iS && iSW; + boolean sR = !iS && iSE; + boolean sD = !iS && (iS_D || isLowest); + boolean tD = !iU && iS_U; + + // RENDER! + + // "above shadow" + if (isShadedAsCube(map, tilePos)) { + if (abvInner) RenderUtils.quadTextured(topLineQuad, MapTx.SH_ABOVE_INNER); + if (abvLeft) RenderUtils.quadTextured(topLineQuad, MapTx.SH_ABOVE_LEFT); + if (abvRight) RenderUtils.quadTextured(topLineQuad, MapTx.SH_ABOVE_RIGHT); + if (abvBoth) RenderUtils.quadTextured(topLineQuad, MapTx.SH_ABOVE_BOTH); + } + + // the tile + TxQuad tx = getTexture(); + + if (!transpS || !isGlassy(map, tilePos)) { + RenderUtils.quadTextured(tileQuad, tx); + } else { + TxQuad tx2 = tx.copy(); + tx2.size.y -= 32; + tx2.uvs.growUp_ip(-32); + RenderUtils.quadTextured(tileQuad.growDown(-32), tx2); + } + + if (map.hasGoal && map.getGoal().equals(tilePos.add(0, 0, 1))) { + RenderUtils.quadTextured(tileQuad.growDown(-32), MapTx.L_GOAL); + } + + if (!isShadedAsCube(map, tilePos)) return; + + // ### TOP FACE ### + glPushMatrix(); + RenderUtils.translate(topMid); + + // RU, U + if (tRU) RenderUtils.quadTextured(topRect, MapTx.SH_TOP_CORNER); + if (tU) RenderUtils.quadTextured(topRect, MapTx.SH_TOP_SIDE); + + glRotated(90, 0, 0, 1); + // LU, L + if (tLU) RenderUtils.quadTextured(topRect, MapTx.SH_TOP_CORNER); + if (tL) RenderUtils.quadTextured(topRect, MapTx.SH_TOP_SIDE); + + glRotated(90, 0, 0, 1); + // LD, D + if (tLD) RenderUtils.quadTextured(topRect, MapTx.SH_TOP_CORNER); + if (tD) RenderUtils.quadTextured(topRect, MapTx.SH_TOP_SIDE); + + glRotated(90, 0, 0, 1); + // RD, R + if (tRD) RenderUtils.quadTextured(topRect, MapTx.SH_TOP_CORNER); + if (tR) RenderUtils.quadTextured(topRect, MapTx.SH_TOP_SIDE); + + glPopMatrix(); + + // ### SIDE FACE ### + glPushMatrix(); + RenderUtils.translate(sideMid); + + // R, D + if (sR) RenderUtils.quadTextured(sideRect, MapTx.SH_SIDE_SIDE); + if (sD) RenderUtils.quadTextured(sideRect, MapTx.SH_SIDE_BOTTOM); + glRotated(180, 0, 0, 1); + // L + if (sL) RenderUtils.quadTextured(sideRect, MapTx.SH_SIDE_SIDE); + + glPopMatrix(); + + if (isLowest) { + RGB sideShadow = new RGB(RGB.WHITE, 0.6); + + // S + if (!iS && !iS_D) { + glPushMatrix(); + RenderUtils.translate(lowFaceMid.add(0, -64)); + RenderUtils.quadTextured(topRect, MapTx.SH_TOP_SIDE, sideShadow); + glPopMatrix(); + } + + // W + if (!iW && !iW_D) { + glPushMatrix(); + RenderUtils.translate(lowFaceMid.add(-64, 0)); + glRotated(-90, 0, 0, 1); + RenderUtils.quadTextured(topRect, MapTx.SH_TOP_SIDE, sideShadow); + glPopMatrix(); + } + + // E + if (!iE && !iE_D) { + glPushMatrix(); + RenderUtils.translate(lowFaceMid.add(64, 0)); + glRotated(90, 0, 0, 1); + RenderUtils.quadTextured(topRect, MapTx.SH_TOP_SIDE, sideShadow); + glPopMatrix(); + } + + // SW corner + if (!iSW && !iS && !iW && !iSW_D && !iS_D && !iW_D) { + glPushMatrix(); + RenderUtils.translate(lowFaceMid.add(-64, -64)); + RenderUtils.quadTextured(topRect, MapTx.SH_TOP_CORNER, sideShadow); + glPopMatrix(); + } + + // SE corner + if (!iSE && !iS && !iE && !iSE_D && !iS_D && !iE_D) { + glPushMatrix(); + RenderUtils.translate(lowFaceMid.add(64, -64)); + glRotated(90, 0, 0, 1); + RenderUtils.quadTextured(topRect, MapTx.SH_TOP_CORNER, sideShadow); + glPopMatrix(); + } + + // NW corner + if (!iNW && !iN && !iW && !iNW_D && !iN_D && !iW_D) { + glPushMatrix(); + RenderUtils.translate(lowFaceMid.add(-64, 64)); + glRotated(-90, 0, 0, 1); + RenderUtils.quadTextured(topRect, MapTx.SH_TOP_CORNER, sideShadow); + glPopMatrix(); + } + + // NE corner + if (!iNE && !iN && !iE && !iNE_D && !iN_D && !iE_D) { + glPushMatrix(); + RenderUtils.translate(lowFaceMid.add(64, 64)); + glRotated(180, 0, 0, 1); + RenderUtils.quadTextured(topRect, MapTx.SH_TOP_CORNER, sideShadow); + glPopMatrix(); + } + } + + } + +} diff --git a/src/net/tortuga/level/map/tiles/MapTileRegistry.java b/src/net/tortuga/level/map/tiles/MapTileRegistry.java new file mode 100644 index 0000000..ee60e34 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/MapTileRegistry.java @@ -0,0 +1,127 @@ +package net.tortuga.level.map.tiles; + + +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +import net.tortuga.level.map.tiles.blocks.*; +import net.tortuga.level.map.tiles.mechanisms.TileCrate; +import net.tortuga.level.map.tiles.mechanisms.TileElevator; +import net.tortuga.level.map.tiles.mechanisms.TileSwitchOff; +import net.tortuga.level.map.tiles.mechanisms.TileSwitchOn; +import net.tortuga.util.Log; + + +/** + * Map tile registry + * + * @author MightyPork + */ +public class MapTileRegistry { + + private static LinkedHashMap> map = new LinkedHashMap>(); + + + /** + * Init map tile registry + */ + public static void init() + { + // colors + + // natural stuff + register(1, TileCobble.class); + register(2, TileDirt.class); + register(3, TilePeebles.class); + register(4, TileWood.class); + register(5, TileIce.class); + register(6, TileLegoRed.class); + register(7, TileLegoBlue.class); + + // rainbow colors + register(100, TileWhite.class); + + // pushable, active etc. + register(200, TileCrate.class); + register(201, TileElevator.class); + register(202, TileSwitchOff.class); + register(203, TileSwitchOn.class); + } + + + /** + * Register a map tile + * + * @param index index + * @param tileClass tile class + */ + public static void register(int index, Class tileClass) + { + if (map.containsKey(index)) { + Log.w("Registered map tile " + tileClass.getSimpleName() + " replaces " + map.get(index).getSimpleName() + " at index " + index); + } + + if (map.containsValue(tileClass)) { + Log.w("Registering map tile " + tileClass.getSimpleName() + " to index " + index + " as duplicate."); + } + + map.put(index, tileClass); + } + + + /** + * Get map tile index from class + * + * @param tileClass tile class + * @return the index + */ + public static int getTileIndex(Class tileClass) + { + if (!map.containsValue(tileClass)) { + Log.w("Map tile " + tileClass.getSimpleName() + " is not registered."); + } + + for (Entry> e : map.entrySet()) { + if (e.getValue() == tileClass) return e.getKey(); + } + + return -1; + } + + + /** + * Convert meta tile to real tile + * + * @param tile checked tile + * @return output tile for Map + */ + private static MapTile processMetaTile(MapTile tile) + { + if (tile instanceof MetaTile) { + return ((MetaTile) tile).createMapTile(); + } + + return tile; + } + + + /** + * Get instance of map tile with given index + * + * @param index tile index + * @return new tile instance + */ + public static MapTile getTile(int index) + { + if (index == 0) return null; + if (!map.containsKey(index)) { + Log.w("There is no map tile registered at index " + index + "."); + } + try { + return processMetaTile(map.get(index).newInstance()); + } catch (Exception e) { + Log.e("Could not instantiate map tile at " + index + ": " + map.get(index).getSimpleName()); + return null; + } + } +} diff --git a/src/net/tortuga/level/map/tiles/MapTx.java b/src/net/tortuga/level/map/tiles/MapTx.java new file mode 100644 index 0000000..f459c50 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/MapTx.java @@ -0,0 +1,94 @@ +package net.tortuga.level.map.tiles; + + +import net.tortuga.textures.Textures; +import net.tortuga.textures.TxQuad; + + +/** + * Textures for map tiles + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +// irrelevant +public class MapTx { + + // nothing + public static TxQuad T_NONE = null; // XXX temporary + + // tiles + public static TxQuad T_WHITE; + public static TxQuad T_WOOD; + public static TxQuad T_ICE; + public static TxQuad T_COBBLE; + public static TxQuad T_DIRT; + public static TxQuad T_LEGO_RED; + public static TxQuad T_LEGO_BLUE; + public static TxQuad T_CRATE; + public static TxQuad T_PEEBLES; + + // layers + public static TxQuad L_GOAL; + public static TxQuad L_GOAL_LOCK; + public static TxQuad L_ELEV_BASE; + public static TxQuad L_ELEV_BASE_OVER; + public static TxQuad L_ELEV_PLATFORM; + public static TxQuad L_SWITCH_OFF; + public static TxQuad L_SWITCH_ON; + + // shadows + public static TxQuad SH_SIDE_SIDE; + public static TxQuad SH_SIDE_BOTTOM; + public static TxQuad SH_TOP_SIDE; + public static TxQuad SH_TOP_CORNER; + public static TxQuad SH_ABOVE_INNER; + public static TxQuad SH_ABOVE_LEFT; + public static TxQuad SH_ABOVE_RIGHT; + public static TxQuad SH_ABOVE_BOTH; + + + /** + * Load + */ + public static void init() + { + // tiles + + // tiles: row1 + T_WHITE = TxQuad.fromSize(Textures.map_tiles, 0, 0, 64, 96); + T_WOOD = TxQuad.fromSize(Textures.map_tiles, 64, 0, 64, 96); + T_ICE = TxQuad.fromSize(Textures.map_tiles, 64 * 2, 0, 64, 96); + T_COBBLE = TxQuad.fromSize(Textures.map_tiles, 64 * 3, 0, 64, 96); + T_DIRT = TxQuad.fromSize(Textures.map_tiles, 64 * 4, 0, 64, 96); + T_LEGO_RED = TxQuad.fromSize(Textures.map_tiles, 64 * 5, 0, 64, 96); + T_LEGO_BLUE = TxQuad.fromSize(Textures.map_tiles, 64 * 6, 0, 64, 96); + T_CRATE = TxQuad.fromSize(Textures.map_tiles, 64 * 7, 0, 64, 96); + // tiles: row2 + T_PEEBLES = TxQuad.fromSize(Textures.map_tiles, 0, 96, 64, 96); + + // 64x64 nothing + T_NONE = TxQuad.fromSize(Textures.map_tiles, 0, 64 * 7, 64, 64); + + // labels - non-block tiles + L_GOAL = TxQuad.fromSize(Textures.map_tiles, 64 * 7, 64 * 7, 64, 64); + L_GOAL_LOCK = TxQuad.fromSize(Textures.map_tiles, 64 * 7, 64 * 6, 64, 64); + L_ELEV_BASE = TxQuad.fromSize(Textures.map_tiles, 64 * 6, 64 * 7, 64, 64); + L_ELEV_BASE_OVER = TxQuad.fromSize(Textures.map_tiles, 64 * 5, 64 * 7, 64, 64); + L_ELEV_PLATFORM = TxQuad.fromSize(Textures.map_tiles, 64 * 4, 64 * 7, 64, 64); + L_SWITCH_OFF = TxQuad.fromSize(Textures.map_tiles, 64 * 3, 64 * 7, 64, 64); + L_SWITCH_ON = TxQuad.fromSize(Textures.map_tiles, 64 * 2, 64 * 7, 64, 64); + + // shading of tiles + SH_SIDE_SIDE = TxQuad.fromSize(Textures.map_shading, 0, 0, 64, 32); + SH_SIDE_BOTTOM = TxQuad.fromSize(Textures.map_shading, 64, 0, 64, 32); + SH_TOP_CORNER = TxQuad.fromSize(Textures.map_shading, 0, 32, 64, 64); + SH_TOP_SIDE = TxQuad.fromSize(Textures.map_shading, 64, 32, 64, 64); + + SH_ABOVE_INNER = TxQuad.fromSize(Textures.map_shading, 0, 96, 64, 32); + SH_ABOVE_LEFT = TxQuad.fromSize(Textures.map_shading, 64, 96, 64, 32); + SH_ABOVE_RIGHT = TxQuad.fromSize(Textures.map_shading, 0, 128, 64, 32); + SH_ABOVE_BOTH = TxQuad.fromSize(Textures.map_shading, 64, 128, 64, 32); + } + +} diff --git a/src/net/tortuga/level/map/tiles/MetaTile.java b/src/net/tortuga/level/map/tiles/MetaTile.java new file mode 100644 index 0000000..1290aac --- /dev/null +++ b/src/net/tortuga/level/map/tiles/MetaTile.java @@ -0,0 +1,29 @@ +package net.tortuga.level.map.tiles; + + +import net.tortuga.textures.TxQuad; + + +/** + * Meta tile used to build map tiles with special configurations. Is used in + * MapTileRegistry with separate ID. + * + * @author MightyPork + */ +public abstract class MetaTile extends MapTile { + + @Override + public TxQuad getTexture() + { + return null; + } + + + /** + * Create the right map tile + * + * @return the wrapped tile + */ + public abstract MapTile createMapTile(); + +} diff --git a/src/net/tortuga/level/map/tiles/blocks/TileCobble.java b/src/net/tortuga/level/map/tiles/blocks/TileCobble.java new file mode 100644 index 0000000..dce2384 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/blocks/TileCobble.java @@ -0,0 +1,17 @@ +package net.tortuga.level.map.tiles.blocks; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; + + +public class TileCobble extends MapTile { + + @Override + public TxQuad getTexture() + { + return MapTx.T_COBBLE; + } + +} diff --git a/src/net/tortuga/level/map/tiles/blocks/TileDirt.java b/src/net/tortuga/level/map/tiles/blocks/TileDirt.java new file mode 100644 index 0000000..66236d6 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/blocks/TileDirt.java @@ -0,0 +1,17 @@ +package net.tortuga.level.map.tiles.blocks; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; + + +public class TileDirt extends MapTile { + + @Override + public TxQuad getTexture() + { + return MapTx.T_DIRT; + } + +} diff --git a/src/net/tortuga/level/map/tiles/blocks/TileIce.java b/src/net/tortuga/level/map/tiles/blocks/TileIce.java new file mode 100644 index 0000000..2bdf932 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/blocks/TileIce.java @@ -0,0 +1,33 @@ +package net.tortuga.level.map.tiles.blocks; + + +import net.tortuga.level.map.TurtleMap; +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; + +import com.porcupine.coord.CoordI; + + +public class TileIce extends MapTile { + + @Override + public TxQuad getTexture() + { + return MapTx.T_ICE; + } + + + @Override + public boolean isSlipperly(TurtleMap map, CoordI pos) + { + return true; + } + + + @Override + public boolean isGlassy(TurtleMap map, CoordI pos) + { + return true; + } +} diff --git a/src/net/tortuga/level/map/tiles/blocks/TileLegoBlue.java b/src/net/tortuga/level/map/tiles/blocks/TileLegoBlue.java new file mode 100644 index 0000000..4b32f9e --- /dev/null +++ b/src/net/tortuga/level/map/tiles/blocks/TileLegoBlue.java @@ -0,0 +1,17 @@ +package net.tortuga.level.map.tiles.blocks; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; + + +public class TileLegoBlue extends MapTile { + + @Override + public TxQuad getTexture() + { + return MapTx.T_LEGO_BLUE; + } + +} diff --git a/src/net/tortuga/level/map/tiles/blocks/TileLegoRed.java b/src/net/tortuga/level/map/tiles/blocks/TileLegoRed.java new file mode 100644 index 0000000..992592a --- /dev/null +++ b/src/net/tortuga/level/map/tiles/blocks/TileLegoRed.java @@ -0,0 +1,17 @@ +package net.tortuga.level.map.tiles.blocks; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; + + +public class TileLegoRed extends MapTile { + + @Override + public TxQuad getTexture() + { + return MapTx.T_LEGO_RED; + } + +} diff --git a/src/net/tortuga/level/map/tiles/blocks/TilePeebles.java b/src/net/tortuga/level/map/tiles/blocks/TilePeebles.java new file mode 100644 index 0000000..eee18c9 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/blocks/TilePeebles.java @@ -0,0 +1,17 @@ +package net.tortuga.level.map.tiles.blocks; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; + + +public class TilePeebles extends MapTile { + + @Override + public TxQuad getTexture() + { + return MapTx.T_PEEBLES; + } + +} diff --git a/src/net/tortuga/level/map/tiles/blocks/TileWhite.java b/src/net/tortuga/level/map/tiles/blocks/TileWhite.java new file mode 100644 index 0000000..67654a3 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/blocks/TileWhite.java @@ -0,0 +1,17 @@ +package net.tortuga.level.map.tiles.blocks; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; + + +public class TileWhite extends MapTile { + + @Override + public TxQuad getTexture() + { + return MapTx.T_WHITE; + } + +} diff --git a/src/net/tortuga/level/map/tiles/blocks/TileWood.java b/src/net/tortuga/level/map/tiles/blocks/TileWood.java new file mode 100644 index 0000000..a43a795 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/blocks/TileWood.java @@ -0,0 +1,17 @@ +package net.tortuga.level.map.tiles.blocks; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; + + +public class TileWood extends MapTile { + + @Override + public TxQuad getTexture() + { + return MapTx.T_WOOD; + } + +} diff --git a/src/net/tortuga/level/map/tiles/mechanisms/TileCrate.java b/src/net/tortuga/level/map/tiles/mechanisms/TileCrate.java new file mode 100644 index 0000000..1e798ff --- /dev/null +++ b/src/net/tortuga/level/map/tiles/mechanisms/TileCrate.java @@ -0,0 +1,26 @@ +package net.tortuga.level.map.tiles.mechanisms; + + +import net.tortuga.level.map.TurtleMap; +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; + +import com.porcupine.coord.CoordI; + + +public class TileCrate extends MapTile { + + @Override + public TxQuad getTexture() + { + return MapTx.T_CRATE; + } + + + @Override + public boolean canBePushed(TurtleMap map, CoordI tilePos) + { + return true; + } +} diff --git a/src/net/tortuga/level/map/tiles/mechanisms/TileElevator.java b/src/net/tortuga/level/map/tiles/mechanisms/TileElevator.java new file mode 100644 index 0000000..58050ef --- /dev/null +++ b/src/net/tortuga/level/map/tiles/mechanisms/TileElevator.java @@ -0,0 +1,183 @@ +package net.tortuga.level.map.tiles.mechanisms; + + +import net.tortuga.level.map.TurtleMap; +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.MoveDir; +import net.tortuga.level.map.entities.mobile.EntityTurtle; +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.AnimDouble; +import net.tortuga.util.RenderUtils; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.coord.Rect; + + +/** + * Piston / elevator tile for turtle and blocks + * + * @author MightyPork + */ +public class TileElevator extends MapTile { + + private boolean isExtended = false; + private boolean inMotion = false; + private AnimDouble moveProgress = new AnimDouble(0); + private Entity movedEntity; + private static final double ANIM_TIME_UP = 1; + private static final double ANIM_TIME_DOWN = 0.3; + + + @Override + public TxQuad getTexture() + { + return null; + } + + + @Override + public boolean isGlassy(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean isSlipperly(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean isSolidForCollision(TurtleMap map, CoordI tilePos) + { + return isExtended || inMotion; + } + + + @Override + public boolean canPushBlocksInto(TurtleMap map, CoordI tilePos) + { + return !isSolidForCollision(map, tilePos); + } + + + @Override + public boolean isShadedAsCube(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean isShadowSide(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean isShadowSurface(TurtleMap map, CoordI tilePos) + { + return isExtended; + } + + + @Override + public boolean canBePickedUp(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean canBePushed(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public void render(Coord mapMin, CoordI tilePos, TurtleMap map) + { + Coord quadMin = mapMin.add(tilePos.x * 64, (map.getSize().y - 1) * 64 - tilePos.y * 64 + tilePos.z * 32); + + Rect lowQuad = Rect.fromSize(quadMin, 64, 64); + Rect platformQuad = lowQuad.copy(); + if (inMotion) { + double fraction = moveProgress.delta(); + platformQuad.add_ip(0, 32 * (isExtended ? 1 - fraction : fraction)); + } else { + platformQuad.add_ip(0, isExtended ? 32 : 0); + } + + TxQuad txBase = MapTx.L_ELEV_BASE; + TxQuad txBaseOver = MapTx.L_ELEV_BASE_OVER; + TxQuad txPlatform = MapTx.L_ELEV_PLATFORM; + + RenderUtils.quadTextured(lowQuad, txBase); + RenderUtils.quadTextured(platformQuad, txPlatform); + RenderUtils.quadTextured(lowQuad, txBaseOver); + // has parts that are rendered over "3D" vertical side of platform to simulate "hole" + + } + + + @Override + public void onEntityEnter(TurtleMap map, CoordI tilePos, Entity entity) + { + if (entity == null) return; + if (entity.moveDir.isVertical()) return; // fallen onto = do nothing + if (!inMotion) { + if (!isExtended) { + inMotion = true; + moveProgress.animIn(ANIM_TIME_UP); + + // let the entity go up + entity.inMotion = true; + entity.moveDir = MoveDir.UP; + entity.moveProgress.animIn(ANIM_TIME_UP); + entity.setImmediateShadow(true); + + movedEntity = entity; + } + } + } + + + @Override + public void onEntityStepOff(TurtleMap map, CoordI tilePos, Entity entity) + { + if (entity == null) return; + if (!(entity instanceof EntityTurtle)) return; + if (!inMotion) { + if (isExtended) { + inMotion = true; // contract + moveProgress.animIn(ANIM_TIME_DOWN); + } + } + } + + + @Override + public void update(TurtleMap map, CoordI tilePos, double delta) + { + if (inMotion) { + moveProgress.update(delta); + if (moveProgress.isFinished()) { + inMotion = false; + isExtended = !isExtended; + + if (movedEntity != null) { + movedEntity.setImmediateShadow(false); + movedEntity = null; + } + } + } + } + +} diff --git a/src/net/tortuga/level/map/tiles/mechanisms/TileSwitch.java b/src/net/tortuga/level/map/tiles/mechanisms/TileSwitch.java new file mode 100644 index 0000000..dd30b64 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/mechanisms/TileSwitch.java @@ -0,0 +1,170 @@ +package net.tortuga.level.map.tiles.mechanisms; + + +import net.tortuga.level.map.ISwitch; +import net.tortuga.level.map.TurtleMap; +import net.tortuga.level.map.entities.Entity; +import net.tortuga.level.map.entities.mobile.EntityBlock; +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MapTx; +import net.tortuga.sounds.Effects; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.RenderUtils; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.coord.Rect; + + +/** + * switch tile + * + * @author MightyPork + */ +public class TileSwitch extends MapTile implements ISwitch { + + /** Is on */ + public boolean activated = false; + + + /** + * Switch tile + */ + public TileSwitch() {} + + + /** + * Switch tile + * + * @param state is activated + */ + public TileSwitch(boolean state) { + activated = state; + } + + + /** + * Set activated + * + * @param state activated + */ + public void setActivated(boolean state) + { + activated = state; + } + + + @Override + public TxQuad getTexture() + { + return null; + } + + + @Override + public boolean canPushBlocksInto(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean isSolidForCollision(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean isShadowSide(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean isShadowSurface(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean isGlassy(TurtleMap map, CoordI tilePos) + { + return true; + } + + + @Override + public boolean isShadedAsCube(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public boolean isSlipperly(TurtleMap map, CoordI tilePos) + { + return false; + } + + + @Override + public void render(Coord mapMin, CoordI tilePos, TurtleMap map) + { + Coord quadMin = mapMin.add(tilePos.x * 64, (map.getSize().y - 1) * 64 - tilePos.y * 64 + tilePos.z * 32); + + Rect lowQuad = Rect.fromSize(quadMin, 64, 64); + + RenderUtils.quadTextured(lowQuad, activated ? MapTx.L_SWITCH_ON : MapTx.L_SWITCH_OFF); + } + + + @Override + public void onEntityEnter(TurtleMap map, CoordI tilePos, Entity entity) + { + if (entity instanceof EntityBlock) entity.setDead(); // fix for destroying switches - ugly but neccessary +// if(entity instanceof EntityTurtle) { +// activated = !activated; +// Effects.play("switch.toggle", 0.7+rand.nextDouble()*0.4, 1); +// } + } + + + /** + * Toggle this switch by turtle + */ + public void switchByTurtle() + { + activated = !activated; + Effects.play("switch.toggle", 0.7 + rand.nextDouble() * 0.4, 1); + } + + + @Override + public MapTile copyFrom(MapTile copied) + { + if (copied instanceof TileSwitch) { + activated = ((TileSwitch) copied).activated; + } + + return this; + } + + + @Override + public void onAddedToMap(TurtleMap map, CoordI tilePos) + { + super.onAddedToMap(map, tilePos); + map.registerSwitch(this); + } + + + @Override + public boolean isOn() + { + return activated; + } + +} diff --git a/src/net/tortuga/level/map/tiles/mechanisms/TileSwitchOff.java b/src/net/tortuga/level/map/tiles/mechanisms/TileSwitchOff.java new file mode 100644 index 0000000..9131999 --- /dev/null +++ b/src/net/tortuga/level/map/tiles/mechanisms/TileSwitchOff.java @@ -0,0 +1,21 @@ +package net.tortuga.level.map.tiles.mechanisms; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MetaTile; + + +/** + * Inactive switch + * + * @author MightyPork + */ +public class TileSwitchOff extends MetaTile { + + @Override + public MapTile createMapTile() + { + return new TileSwitch(false); + } + +} diff --git a/src/net/tortuga/level/map/tiles/mechanisms/TileSwitchOn.java b/src/net/tortuga/level/map/tiles/mechanisms/TileSwitchOn.java new file mode 100644 index 0000000..eed669e --- /dev/null +++ b/src/net/tortuga/level/map/tiles/mechanisms/TileSwitchOn.java @@ -0,0 +1,21 @@ +package net.tortuga.level.map.tiles.mechanisms; + + +import net.tortuga.level.map.tiles.MapTile; +import net.tortuga.level.map.tiles.MetaTile; + + +/** + * Active switch + * + * @author MightyPork + */ +public class TileSwitchOn extends MetaTile { + + @Override + public MapTile createMapTile() + { + return new TileSwitch(true); + } + +} diff --git a/src/net/tortuga/level/program/GrainList.java b/src/net/tortuga/level/program/GrainList.java new file mode 100644 index 0000000..dcfc8ad --- /dev/null +++ b/src/net/tortuga/level/program/GrainList.java @@ -0,0 +1,20 @@ +package net.tortuga.level.program; + + +import net.tortuga.CustomIonMarks; + +import com.porcupine.ion.AbstractIonList; + + +public class GrainList extends AbstractIonList { + + public GrainList() {} + + + @Override + public byte ionMark() + { + return CustomIonMarks.PGM_GRAIN_LIST; + } + +} diff --git a/src/net/tortuga/level/program/GrainRegistry.java b/src/net/tortuga/level/program/GrainRegistry.java new file mode 100644 index 0000000..c724467 --- /dev/null +++ b/src/net/tortuga/level/program/GrainRegistry.java @@ -0,0 +1,99 @@ +package net.tortuga.level.program; + + +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.grains.*; +import net.tortuga.util.Log; + + +/** + * Grain program tile registry + * + * @author MightyPork + */ +public class GrainRegistry { + + private static LinkedHashMap> map = new LinkedHashMap>(); + + + /** + * Init grain registry + */ + public static void init() + { + // Values & variables + register(1001, GrainNumber.class); + register(1002, GrainVariable.class); + + // Jumps + register(1101, GrainJumpLabel.class); + register(1102, GrainJumpStart.class); + register(1103, GrainJumpEnd.class); + register(1104, GrainJumpSkipOne.class); + register(1105, GrainJumpAbsolute.class); + register(1106, GrainJumpRelative.class); + } + + + /** + * Register an attribute tile + * + * @param index index (>1000) + * @param grainClass attribute class + */ + public static void register(int index, Class grainClass) + { + if (map.containsKey(index)) { + Log.w("Registered attribute tile " + grainClass.getSimpleName() + " replaces " + map.get(index).getSimpleName() + " at index " + index); + } + + if (map.containsValue(grainClass)) { + Log.w("Registering attribute tile " + grainClass.getSimpleName() + " to index " + index + " as duplicate."); + } + + map.put(index, grainClass); + } + + + /** + * Get grain index from class + * + * @param grainClass grain class + * @return the index + */ + public static int getGrainIndex(Class grainClass) + { + if (!map.containsValue(grainClass)) { + Log.w("Attribute tile " + grainClass.getSimpleName() + " is not registered."); + } + + for (Entry> e : map.entrySet()) { + if (e.getValue() == grainClass) return e.getKey(); + } + + return -1; + } + + + /** + * Get instance of grain with given index + * + * @param index grain index + * @return new grain instance + */ + public static ProgTileGrain getGrain(int index) + { + if (!map.containsKey(index)) { + Log.w("There is no attribute tile registered at index " + index + "."); + } + try { + return map.get(index).newInstance(); + } catch (Exception e) { + Log.e("Could not instantiate attribute tile at " + index + ": " + map.get(index).getSimpleName()); + return null; + } + } +} diff --git a/src/net/tortuga/level/program/StoneList.java b/src/net/tortuga/level/program/StoneList.java new file mode 100644 index 0000000..eb8cd6f --- /dev/null +++ b/src/net/tortuga/level/program/StoneList.java @@ -0,0 +1,20 @@ +package net.tortuga.level.program; + + +import net.tortuga.CustomIonMarks; + +import com.porcupine.ion.AbstractIonList; + + +public class StoneList extends AbstractIonList { + + public StoneList() {} + + + @Override + public byte ionMark() + { + return CustomIonMarks.PGM_STONE_LIST; + } + +} diff --git a/src/net/tortuga/level/program/StoneRegistry.java b/src/net/tortuga/level/program/StoneRegistry.java new file mode 100644 index 0000000..3b8a8fa --- /dev/null +++ b/src/net/tortuga/level/program/StoneRegistry.java @@ -0,0 +1,130 @@ +package net.tortuga.level.program; + + +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +import net.tortuga.level.program.tiles.ProgTileStone; +import net.tortuga.level.program.tiles.stones.*; +import net.tortuga.util.Log; + + +/** + * Stone program tile registry + * + * @author MightyPork + */ +public class StoneRegistry { + + private static LinkedHashMap> map = new LinkedHashMap>(); + + + /** + * Init stone registry + */ + public static void init() + { + // movement and rotation + register(1, StoneGoForward.class); + register(2, StoneGoBackward.class); + register(3, StoneTurnLeft.class); + register(4, StoneTurnRight.class); + + // world manipulation and conditional jumps + register(101, StoneBoxTake.class); + register(102, StoneBoxPlace.class); + register(103, StoneLookFront.class); + register(104, StoneLookDown.class); + register(105, StoneLookInv.class); + + // labels and jumps + register(201, StoneLabel.class); + register(202, StoneGoto.class); + register(203, StoneCall.class); + register(204, StoneReturn.class); + + // special operations + register(301, StoneBell.class); + register(302, StoneSleep.class); + + // comparations + register(401, StoneCompare.class); + register(402, StoneCompareResultFull.class); + register(403, StoneCompareResultSimple.class); + + // math operations + register(501, StoneMathSet.class); + register(502, StoneMathAdd.class); + register(503, StoneMathSub.class); + register(504, StoneMathMul.class); + register(505, StoneMathDiv.class); + register(506, StoneMathMod.class); + register(507, StoneMathInc.class); + register(508, StoneMathDecJz.class); + register(509, StoneMathDecJnz.class); + register(510, StoneMathAnd.class); + register(511, StoneMathOr.class); + register(512, StoneMathXor.class); + register(513, StoneMathNot.class); + } + + + /** + * Register a program stone tile + * + * @param index index (<1000) + * @param stoneClass stone class + */ + public static void register(int index, Class stoneClass) + { + if (map.containsKey(index)) { + Log.w("Registered program tile " + stoneClass.getSimpleName() + " replaces " + map.get(index).getSimpleName() + " at index " + index); + } + + if (map.containsValue(stoneClass)) { + Log.w("Registering program tile " + stoneClass.getSimpleName() + " to index " + index + " as duplicate."); + } + + map.put(index, stoneClass); + } + + + /** + * Get stone index from class + * + * @param stoneClass stone class + * @return the index + */ + public static int getStoneIndex(Class stoneClass) + { + if (!map.containsValue(stoneClass)) { + Log.w("Program tile " + stoneClass.getSimpleName() + " is not registered."); + } + + for (Entry> e : map.entrySet()) { + if (e.getValue() == stoneClass) return e.getKey(); + } + + return -1; + } + + + /** + * Get instance of stone with given index + * + * @param index stone index + * @return new stone instance + */ + public static ProgTileStone getStone(int index) + { + if (!map.containsKey(index)) { + Log.w("There is no program tile registered at index " + index + "."); + } + try { + return map.get(index).newInstance(); + } catch (Exception e) { + Log.e("Could not instantiate program tile at " + index + ": " + map.get(index).getSimpleName()); + return null; + } + } +} diff --git a/src/net/tortuga/level/program/tiles/CompiledStone.java b/src/net/tortuga/level/program/tiles/CompiledStone.java new file mode 100644 index 0000000..ca7aeee --- /dev/null +++ b/src/net/tortuga/level/program/tiles/CompiledStone.java @@ -0,0 +1,80 @@ +package net.tortuga.level.program.tiles; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; + + +/** + * Compiled program stone + * + * @author MightyPork + */ +public class CompiledStone { + + private ProgTileStone stone = null; + private ProgTileGrain grainTop = null; + private ProgTileGrain grainBottom = null; + + + /** + * A compiled stone + * + * @param stone main stone + * @param grainTop top grain if any + * @param grainBottom bottom grain if any + */ + public CompiledStone(ProgTileStone stone, ProgTileGrain grainTop, ProgTileGrain grainBottom) { + this.stone = stone; + this.grainTop = grainTop; + this.grainBottom = grainBottom; + } + + + /** + * Get main stone + * + * @return main stone + */ + public ProgTileStone getStone() + { + return stone; + } + + + /** + * Get top grain + * + * @return top grain + */ + public ProgTileGrain getGrainTop() + { + return grainTop; + } + + + /** + * Get bottom grain + * + * @return bottom grain + */ + public ProgTileGrain getGrainBottom() + { + return grainBottom; + } + + + /** + * Execute this instruction + * + * @param environment runtime environment + * @param turtle turtle controller + * @throws TurtleRuntimeException + */ + public void execute(TurtleRuntimeEnvironment environment, TurtleController turtle) throws TurtleRuntimeException + { + stone.execute(grainTop, grainBottom, environment, turtle); + } + +} diff --git a/src/net/tortuga/level/program/tiles/EGrainSlotMode.java b/src/net/tortuga/level/program/tiles/EGrainSlotMode.java new file mode 100644 index 0000000..cc7a0f2 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/EGrainSlotMode.java @@ -0,0 +1,7 @@ +package net.tortuga.level.program.tiles; + + +public enum EGrainSlotMode +{ + REQUIRED, OPTIONAL, UNUSED; +} diff --git a/src/net/tortuga/level/program/tiles/EGrainType.java b/src/net/tortuga/level/program/tiles/EGrainType.java new file mode 100644 index 0000000..03b4209 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/EGrainType.java @@ -0,0 +1,19 @@ +package net.tortuga.level.program.tiles; + + +/** + * Grain type + * + * @author MightyPork + */ +public enum EGrainType +{ + /** Do-nothing grain */ + NONE, + /** Jump target, with working "execute" method. */ + LABEL, + /** Number from user */ + NUMBER, + /** Variable identifier */ + VAR; +} diff --git a/src/net/tortuga/level/program/tiles/EProgTileType.java b/src/net/tortuga/level/program/tiles/EProgTileType.java new file mode 100644 index 0000000..8ecb3ca --- /dev/null +++ b/src/net/tortuga/level/program/tiles/EProgTileType.java @@ -0,0 +1,15 @@ +package net.tortuga.level.program.tiles; + + +/** + * Kind of program tile + * + * @author MightyPork + */ +public enum EProgTileType +{ + /** Main stone */ + STONE, + /** Argument grain */ + GRAIN +} diff --git a/src/net/tortuga/level/program/tiles/ProgTileBase.java b/src/net/tortuga/level/program/tiles/ProgTileBase.java new file mode 100644 index 0000000..0553da6 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/ProgTileBase.java @@ -0,0 +1,104 @@ +package net.tortuga.level.program.tiles; + + +import net.tortuga.textures.Textures; +import net.tortuga.textures.Tx; +import net.tortuga.textures.TxQuad; +import net.tortuga.util.RenderUtils; + +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.coord.Rect; + + +public abstract class ProgTileBase { + + public int variant = 0; + + + public int getVariant() + { + return variant; + } + + + public ProgTileBase setVariant(int i) + { + this.variant = i; + return this; + } + + + public abstract boolean isSingleInstance(); + + + public abstract CoordI getOverlayIndex(); + + + public abstract CoordI getSubOverlayIndex(); + + + public abstract RGB getSubOverlayColor(); + + + public abstract EProgTileType getProgTileType(); + + + public abstract RGB getStoneColor(); + + + public void render(Coord posCenter) + { + Rect tileRect = new Rect(posCenter, posCenter).grow_ip(32, 32); + Rect overlayRect = new Rect(posCenter, posCenter).grow_ip(24, 24); + + TxQuad tx = (getProgTileType() == EProgTileType.STONE ? Tx.STONE_SQUARE : Tx.STONE_HEXAGON); + + RGB color = getStoneColor(); + RGB color0 = getSubOverlayColor(); + + RenderUtils.quadTextured(tileRect, tx, color); + + CoordI index0 = getSubOverlayIndex(); + if (index0 != null) { + Rect overlayUvs0 = new Rect(index0.toCoord(), index0.toCoord().add_ip(1, 1)).mul_ip(48); + RenderUtils.quadTextured(overlayRect, overlayUvs0, Textures.program, color0); + } + + CoordI index = getOverlayIndex(); + Rect overlayUvs = new Rect(index.toCoord(), index.toCoord().add_ip(1, 1)).mul_ip(48); + RenderUtils.quadTextured(overlayRect, overlayUvs, Textures.program, color); + } + + + public final ProgTileBase copy() + { + try { + ProgTileBase stone = getClass().newInstance(); + stone.copyFrom(this); + return stone; + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + return null; + } + + + public void copyFrom(ProgTileBase other) + { + variant = other.variant; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null || !(obj instanceof ProgTileBase)) return false; + return obj.getClass() == getClass() && ((ProgTileBase) obj).getVariant() == getVariant(); + } + +} diff --git a/src/net/tortuga/level/program/tiles/ProgTileGrain.java b/src/net/tortuga/level/program/tiles/ProgTileGrain.java new file mode 100644 index 0000000..7c5479d --- /dev/null +++ b/src/net/tortuga/level/program/tiles/ProgTileGrain.java @@ -0,0 +1,88 @@ +package net.tortuga.level.program.tiles; + + +import net.tortuga.annotations.Unimplemented; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public abstract class ProgTileGrain extends ProgTileBase { + + public ProgTileGrain() {} + + + @Override + public abstract CoordI getOverlayIndex(); + + + @Override + public final EProgTileType getProgTileType() + { + return EProgTileType.GRAIN; + } + + + @Override + public abstract RGB getStoneColor(); + + + public abstract EGrainType getGrainType(); + + + @Override + public boolean isSingleInstance() + { + return false; + } + + + @Override + public RGB getSubOverlayColor() + { + return RGB.WHITE; + } + + + @Override + public CoordI getSubOverlayIndex() + { + return null; + } + + + /** + * Jump to target label or address + * + * @param tre turtle runtime environment + * @throws TurtleRuntimeException + */ + @Unimplemented + public void doJump(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + {} + + + public boolean hasExtraNumber() + { + return false; + } + + + public int getExtraNumber() + { + return 0; + } + + + @Unimplemented + public void setExtraNumber(int num) + {} + + + @Unimplemented + public void doCall(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + {} + +} diff --git a/src/net/tortuga/level/program/tiles/ProgTileStone.java b/src/net/tortuga/level/program/tiles/ProgTileStone.java new file mode 100644 index 0000000..84663bd --- /dev/null +++ b/src/net/tortuga/level/program/tiles/ProgTileStone.java @@ -0,0 +1,76 @@ +package net.tortuga.level.program.tiles; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public abstract class ProgTileStone extends ProgTileBase { + + public abstract boolean acceptsGrainTop(EGrainType grainType); + + + public abstract boolean acceptsGrainBottom(EGrainType grainType); + + + public abstract EGrainSlotMode getGrainModeTop(); + + + public abstract EGrainSlotMode getGrainModeBottom(); + + + public ProgTileStone() {} + + + @Override + public abstract CoordI getOverlayIndex(); + + + @Override + public EProgTileType getProgTileType() + { + return EProgTileType.STONE; + } + + + @Override + public abstract RGB getStoneColor(); + + + @Override + public boolean isSingleInstance() + { + return false; + } + + + @Override + public RGB getSubOverlayColor() + { + return RGB.WHITE; + } + + + @Override + public CoordI getSubOverlayIndex() + { + return null; + } + + + /** + * Execute this command stone (in case of jump grain, perform the jump) + * + * @param topGrain top grain argument + * @param bottomGrain bottom grain argument + * @param tre turtle runtime environment + * @param tc turtle controller with world access + * @throws TurtleRuntimeException + */ + public abstract void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) throws TurtleRuntimeException; + +} diff --git a/src/net/tortuga/level/program/tiles/StoneColors.java b/src/net/tortuga/level/program/tiles/StoneColors.java new file mode 100644 index 0000000..c89f271 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/StoneColors.java @@ -0,0 +1,36 @@ +package net.tortuga.level.program.tiles; + + +import com.porcupine.color.RGB; + + +public class StoneColors { + + public static final RGB MOVEMENT = new RGB(0xe0a526); + public static final RGB JUMPS = new RGB(0x1897f1); + public static final RGB MATH = new RGB(0xffff00); + public static final RGB TESTS = new RGB(0xb746ff); + public static final RGB SPECIAL = new RGB(0xff2929); + + public static final RGB GRAIN_LABELS = new RGB(0xffddaa); + + public static final RGB GRAIN_NUMBER = new RGB(0x0EC9C9); + public static final RGB GRAIN_VAR = new RGB(0xF5F540); + + public static final RGB[] LABEL_COLORS = { new RGB(0xca1315), // red + new RGB(0xe1bd14), // yellow + new RGB(0x06c61d), // green + new RGB(0x1357ac), // blue + new RGB(0xe8561c), // orange + new RGB(0x11c3ac), // cyan + new RGB(0xac26d9), // violet + new RGB(0x005a0a), // dark green + new RGB(0xff83fb), // pink + new RGB(0xa2005f), // purple + new RGB(0x8bdaff), // sky blue + new RGB(0xf4f4f4), // white + new RGB(0x232323), // black + new RGB(0xb3b3b3), // gray + new RGB(0x7f4500), // brown + }; +} diff --git a/src/net/tortuga/level/program/tiles/StoneTx.java b/src/net/tortuga/level/program/tiles/StoneTx.java new file mode 100644 index 0000000..73a2f1e --- /dev/null +++ b/src/net/tortuga/level/program/tiles/StoneTx.java @@ -0,0 +1,90 @@ +package net.tortuga.level.program.tiles; + + +import com.porcupine.coord.CoordI; + + +public class StoneTx { + + public static final CoordI GO_FORTH = new CoordI(0, 0); + public static final CoordI GO_BACK = new CoordI(1, 0); + public static final CoordI TURN_LEFT = new CoordI(2, 0); + public static final CoordI TURN_RIGHT = new CoordI(3, 0); + public static final CoordI BOX_PLACE = new CoordI(4, 0); + public static final CoordI BOX_TAKE = new CoordI(5, 0); + public static final CoordI SLEEP = new CoordI(6, 0); + public static final CoordI BELL = new CoordI(7, 0); + + public static final CoordI LABEL = new CoordI(0, 1); + public static final CoordI GOTO = new CoordI(1, 1); + public static final CoordI CALL = new CoordI(2, 1); + public static final CoordI RETURN = new CoordI(3, 1); + public static final CoordI LABEL_FILL = new CoordI(4, 1); + + public static final CoordI MATH_SET = new CoordI(0, 2); + public static final CoordI MATH_ADD = new CoordI(1, 2); + public static final CoordI MATH_SUB = new CoordI(2, 2); + public static final CoordI MATH_MUL = new CoordI(3, 2); + public static final CoordI MATH_DIV = new CoordI(4, 2); + public static final CoordI MATH_MOD = new CoordI(5, 2); + + public static final CoordI MATH_AND = new CoordI(0, 3); + public static final CoordI MATH_OR = new CoordI(1, 3); + public static final CoordI MATH_XOR = new CoordI(2, 3); + public static final CoordI MATH_NOT = new CoordI(3, 3); + public static final CoordI MATH_INC = new CoordI(4, 3); + public static final CoordI MATH_DEC_JZ = new CoordI(5, 3); + public static final CoordI MATH_DEC_JNZ = new CoordI(6, 3); + + public static final CoordI COMPARE = new CoordI(0, 4); + public static final CoordI COMPARE_RESULT_FULL = new CoordI(1, 4); + public static final CoordI COMPARE_RESULT_EQUAL_UNEQUAL = new CoordI(2, 4); + + public static final CoordI LOOK_FRONT = new CoordI(3, 4); + public static final CoordI LOOK_INV = new CoordI(4, 4); + public static final CoordI LOOK_DOWN = new CoordI(5, 4); + + public static final CoordI GRAIN_LABEL = new CoordI(0, 6); + public static final CoordI GRAIN_NUMBER = new CoordI(1, 6); + public static final CoordI GRAIN_JUMP_SKIP_ONE = new CoordI(2, 6); + public static final CoordI GRAIN_JUMP_RELATIVE_FORTH = new CoordI(3, 6); + public static final CoordI GRAIN_JUMP_RELATIVE_BACK = new CoordI(4, 6); + public static final CoordI GRAIN_JUMP_END = new CoordI(5, 6); + public static final CoordI GRAIN_JUMP_START = new CoordI(6, 6); + public static final CoordI GRAIN_JUMP_ABSOLUTE = new CoordI(7, 6); + public static final CoordI GRAIN_LABEL_FILL = new CoordI(8, 6); + + public static final CoordI GRAIN_VAR_A = new CoordI(0, 7); + public static final CoordI GRAIN_VAR_B = new CoordI(1, 7); + public static final CoordI GRAIN_VAR_C = new CoordI(2, 7); + public static final CoordI GRAIN_VAR_D = new CoordI(3, 7); + public static final CoordI GRAIN_VAR_E = new CoordI(4, 7); + public static final CoordI GRAIN_VAR_F = new CoordI(5, 7); + public static final CoordI GRAIN_VAR_G = new CoordI(6, 7); + public static final CoordI GRAIN_VAR_H = new CoordI(7, 7); + public static final CoordI GRAIN_VAR_I = new CoordI(8, 7); + public static final CoordI GRAIN_VAR_J = new CoordI(9, 7); + + public static final CoordI GRAIN_VAR_K = new CoordI(0, 8); + public static final CoordI GRAIN_VAR_L = new CoordI(1, 8); + public static final CoordI GRAIN_VAR_M = new CoordI(2, 8); + public static final CoordI GRAIN_VAR_N = new CoordI(3, 8); + public static final CoordI GRAIN_VAR_O = new CoordI(4, 8); + public static final CoordI GRAIN_VAR_P = new CoordI(5, 8); + public static final CoordI GRAIN_VAR_Q = new CoordI(6, 8); + public static final CoordI GRAIN_VAR_R = new CoordI(7, 8); + public static final CoordI GRAIN_VAR_S = new CoordI(8, 8); + public static final CoordI GRAIN_VAR_T = new CoordI(9, 8); + + public static final CoordI GRAIN_VAR_U = new CoordI(0, 9); + public static final CoordI GRAIN_VAR_V = new CoordI(1, 9); + public static final CoordI GRAIN_VAR_W = new CoordI(2, 9); + public static final CoordI GRAIN_VAR_X = new CoordI(3, 9); + public static final CoordI GRAIN_VAR_Y = new CoordI(4, 9); + public static final CoordI GRAIN_VAR_Z = new CoordI(5, 9); + + public static final CoordI[] GRAIN_LETTERS = { GRAIN_VAR_A, GRAIN_VAR_B, GRAIN_VAR_C, GRAIN_VAR_D, GRAIN_VAR_E, GRAIN_VAR_F, GRAIN_VAR_G, GRAIN_VAR_H, GRAIN_VAR_I, GRAIN_VAR_J, GRAIN_VAR_K, + GRAIN_VAR_L, GRAIN_VAR_M, GRAIN_VAR_N, GRAIN_VAR_O, GRAIN_VAR_P, GRAIN_VAR_Q, GRAIN_VAR_R, GRAIN_VAR_S, GRAIN_VAR_T, GRAIN_VAR_U, GRAIN_VAR_V, GRAIN_VAR_W, GRAIN_VAR_X, GRAIN_VAR_Y, + GRAIN_VAR_Z }; + +} diff --git a/src/net/tortuga/level/program/tiles/grains/GrainJumpAbsolute.java b/src/net/tortuga/level/program/tiles/grains/GrainJumpAbsolute.java new file mode 100644 index 0000000..f858ccc --- /dev/null +++ b/src/net/tortuga/level/program/tiles/grains/GrainJumpAbsolute.java @@ -0,0 +1,85 @@ +package net.tortuga.level.program.tiles.grains; + + +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.EGrainType; +import net.tortuga.level.program.tiles.ProgTileBase; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.StoneColors; +import net.tortuga.level.program.tiles.StoneTx; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; +import com.porcupine.math.Calc; + + +public class GrainJumpAbsolute extends ProgTileGrain { + + private int address = 1; + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GRAIN_JUMP_ABSOLUTE; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.GRAIN_LABELS; + } + + + @Override + public EGrainType getGrainType() + { + return EGrainType.LABEL; + } + + + @Override + public void doJump(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + { + tre.jumpAbsolute(address - 1); + } + + + @Override + public void doCall(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + { + tre.pushAddress(); + tre.jumpAbsolute(address - 1); + } + + + @Override + public boolean hasExtraNumber() + { + return true; + } + + + @Override + public int getExtraNumber() + { + return address; + } + + + @Override + public void setExtraNumber(int num) + { + address = Calc.clampi(num, 1); + } + + + @Override + public void copyFrom(ProgTileBase other) + { + super.copyFrom(other); + setExtraNumber(((ProgTileGrain) other).getExtraNumber()); + } +} diff --git a/src/net/tortuga/level/program/tiles/grains/GrainJumpEnd.java b/src/net/tortuga/level/program/tiles/grains/GrainJumpEnd.java new file mode 100644 index 0000000..9450775 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/grains/GrainJumpEnd.java @@ -0,0 +1,43 @@ +package net.tortuga.level.program.tiles.grains; + + +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.EGrainType; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.StoneColors; +import net.tortuga.level.program.tiles.StoneTx; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class GrainJumpEnd extends ProgTileGrain { + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GRAIN_JUMP_END; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.GRAIN_LABELS; + } + + + @Override + public EGrainType getGrainType() + { + return EGrainType.LABEL; + } + + + @Override + public void doJump(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + { + tre.jumpEnd(); + } +} diff --git a/src/net/tortuga/level/program/tiles/grains/GrainJumpLabel.java b/src/net/tortuga/level/program/tiles/grains/GrainJumpLabel.java new file mode 100644 index 0000000..b858d41 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/grains/GrainJumpLabel.java @@ -0,0 +1,66 @@ +package net.tortuga.level.program.tiles.grains; + + +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.EGrainType; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.StoneColors; +import net.tortuga.level.program.tiles.StoneTx; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class GrainJumpLabel extends ProgTileGrain { + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GRAIN_LABEL; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.GRAIN_LABELS; + } + + + @Override + public EGrainType getGrainType() + { + return EGrainType.LABEL; + } + + + @Override + public RGB getSubOverlayColor() + { + return StoneColors.LABEL_COLORS[variant]; + } + + + @Override + public CoordI getSubOverlayIndex() + { + return StoneTx.GRAIN_LABEL_FILL; + } + + + @Override + public void doJump(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + { + tre.jumpToLabel(variant); + } + + + @Override + public void doCall(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + { + tre.pushAddress(); + tre.jumpToLabel(variant); + } + +} diff --git a/src/net/tortuga/level/program/tiles/grains/GrainJumpRelative.java b/src/net/tortuga/level/program/tiles/grains/GrainJumpRelative.java new file mode 100644 index 0000000..e3ac2e8 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/grains/GrainJumpRelative.java @@ -0,0 +1,85 @@ +package net.tortuga.level.program.tiles.grains; + + +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.EGrainType; +import net.tortuga.level.program.tiles.ProgTileBase; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.StoneColors; +import net.tortuga.level.program.tiles.StoneTx; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class GrainJumpRelative extends ProgTileGrain { + + private int increment = 0; + + + @Override + public CoordI getOverlayIndex() + { + return increment >= 0 ? StoneTx.GRAIN_JUMP_RELATIVE_FORTH : StoneTx.GRAIN_JUMP_RELATIVE_BACK; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.GRAIN_LABELS; + } + + + @Override + public EGrainType getGrainType() + { + return EGrainType.LABEL; + } + + + @Override + public void doJump(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + { + tre.jumpRelative(increment); + } + + + @Override + public void doCall(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + { + tre.pushAddress(); + tre.jumpRelative(-1 + increment); + } + + + @Override + public boolean hasExtraNumber() + { + return true; + } + + + @Override + public int getExtraNumber() + { + return increment; + } + + + @Override + public void setExtraNumber(int num) + { + increment = num; + } + + + @Override + public void copyFrom(ProgTileBase other) + { + super.copyFrom(other); + setExtraNumber(((ProgTileGrain) other).getExtraNumber()); + } + +} diff --git a/src/net/tortuga/level/program/tiles/grains/GrainJumpSkipOne.java b/src/net/tortuga/level/program/tiles/grains/GrainJumpSkipOne.java new file mode 100644 index 0000000..18f0d41 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/grains/GrainJumpSkipOne.java @@ -0,0 +1,44 @@ +package net.tortuga.level.program.tiles.grains; + + +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.EGrainType; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.StoneColors; +import net.tortuga.level.program.tiles.StoneTx; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class GrainJumpSkipOne extends ProgTileGrain { + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GRAIN_JUMP_SKIP_ONE; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.GRAIN_LABELS; + } + + + @Override + public EGrainType getGrainType() + { + return EGrainType.LABEL; + } + + + @Override + public void doJump(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + { + tre.jumpRelative(1); + } + +} diff --git a/src/net/tortuga/level/program/tiles/grains/GrainJumpStart.java b/src/net/tortuga/level/program/tiles/grains/GrainJumpStart.java new file mode 100644 index 0000000..d57750c --- /dev/null +++ b/src/net/tortuga/level/program/tiles/grains/GrainJumpStart.java @@ -0,0 +1,44 @@ +package net.tortuga.level.program.tiles.grains; + + +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.EGrainType; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.StoneColors; +import net.tortuga.level.program.tiles.StoneTx; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class GrainJumpStart extends ProgTileGrain { + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GRAIN_JUMP_START; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.GRAIN_LABELS; + } + + + @Override + public EGrainType getGrainType() + { + return EGrainType.LABEL; + } + + + @Override + public void doJump(TurtleRuntimeEnvironment tre) throws TurtleRuntimeException + { + tre.jumpStart(); + } + +} diff --git a/src/net/tortuga/level/program/tiles/grains/GrainNumber.java b/src/net/tortuga/level/program/tiles/grains/GrainNumber.java new file mode 100644 index 0000000..a7720f1 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/grains/GrainNumber.java @@ -0,0 +1,67 @@ +package net.tortuga.level.program.tiles.grains; + + +import net.tortuga.level.program.tiles.EGrainType; +import net.tortuga.level.program.tiles.ProgTileBase; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.StoneColors; +import net.tortuga.level.program.tiles.StoneTx; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class GrainNumber extends ProgTileGrain { + + private int number = 0; + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GRAIN_NUMBER; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.GRAIN_NUMBER; + } + + + @Override + public EGrainType getGrainType() + { + return EGrainType.NUMBER; + } + + + @Override + public boolean hasExtraNumber() + { + return true; + } + + + @Override + public int getExtraNumber() + { + return number; + } + + + @Override + public void setExtraNumber(int num) + { + this.number = num; + } + + + @Override + public void copyFrom(ProgTileBase other) + { + super.copyFrom(other); + setExtraNumber(((ProgTileGrain) other).getExtraNumber()); + } +} diff --git a/src/net/tortuga/level/program/tiles/grains/GrainVariable.java b/src/net/tortuga/level/program/tiles/grains/GrainVariable.java new file mode 100644 index 0000000..90b83f4 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/grains/GrainVariable.java @@ -0,0 +1,38 @@ +package net.tortuga.level.program.tiles.grains; + + +import net.tortuga.level.program.tiles.EGrainType; +import net.tortuga.level.program.tiles.ProgTileGrain; +import net.tortuga.level.program.tiles.StoneColors; +import net.tortuga.level.program.tiles.StoneTx; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class GrainVariable extends ProgTileGrain { + + private int number = 0; + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GRAIN_LETTERS[variant]; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.GRAIN_VAR; + } + + + @Override + public EGrainType getGrainType() + { + return EGrainType.VAR; + } + +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneBell.java b/src/net/tortuga/level/program/tiles/stones/StoneBell.java new file mode 100644 index 0000000..c6d9b70 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneBell.java @@ -0,0 +1,64 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; +import net.tortuga.sounds.Effects; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneBell extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.BELL; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.SPECIAL; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + Effects.play("turtle.bell"); + tre.addSleep(1); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneBoxPlace.java b/src/net/tortuga/level/program/tiles/stones/StoneBoxPlace.java new file mode 100644 index 0000000..7397edb --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneBoxPlace.java @@ -0,0 +1,61 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneBoxPlace extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.BOX_PLACE; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.SPECIAL; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + // FIXME implement BoxPlace + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneBoxTake.java b/src/net/tortuga/level/program/tiles/stones/StoneBoxTake.java new file mode 100644 index 0000000..6fc876a --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneBoxTake.java @@ -0,0 +1,61 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneBoxTake extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.BOX_TAKE; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.SPECIAL; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + // FIXME implement BoxTake + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneCall.java b/src/net/tortuga/level/program/tiles/stones/StoneCall.java new file mode 100644 index 0000000..8402c6c --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneCall.java @@ -0,0 +1,62 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneCall extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.CALL; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.JUMPS; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) throws TurtleRuntimeException + { + topGrain.doCall(tre); + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneCompare.java b/src/net/tortuga/level/program/tiles/stones/StoneCompare.java new file mode 100644 index 0000000..8220b65 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneCompare.java @@ -0,0 +1,79 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneCompare extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.COMPARE; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.TESTS; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int val1; + + if (topGrain.getGrainType() == EGrainType.VAR) { + val1 = tre.getVariable(topGrain.getVariant()); + } else { + val1 = topGrain.getExtraNumber(); + } + + int val2; + + if (bottomGrain.getGrainType() == EGrainType.VAR) { + val2 = tre.getVariable(bottomGrain.getVariant()); + } else { + val2 = bottomGrain.getExtraNumber(); + } + + tre.compareTop = val1; + tre.compareBottom = val2; + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneCompareResultFull.java b/src/net/tortuga/level/program/tiles/stones/StoneCompareResultFull.java new file mode 100644 index 0000000..b6fcf72 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneCompareResultFull.java @@ -0,0 +1,75 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneCompareResultFull extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.COMPARE_RESULT_FULL; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.TESTS; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) throws TurtleRuntimeException + { + if (!(tre.lastStone.getStone() instanceof StoneCompare)) { + throw new TurtleRuntimeException("Result block not after compare."); + } + + if (tre.compareTop > tre.compareBottom) { + if (topGrain != null) topGrain.doJump(tre); + return; + } + + if (tre.compareTop < tre.compareBottom) { + if (bottomGrain != null) bottomGrain.doJump(tre); + return; + } + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneCompareResultSimple.java b/src/net/tortuga/level/program/tiles/stones/StoneCompareResultSimple.java new file mode 100644 index 0000000..a9f238c --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneCompareResultSimple.java @@ -0,0 +1,75 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneCompareResultSimple extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.COMPARE_RESULT_EQUAL_UNEQUAL; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.TESTS; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) throws TurtleRuntimeException + { + if (!(tre.lastStone.getStone() instanceof StoneCompare)) { + throw new TurtleRuntimeException("Result block not after compare."); + } + + if (tre.compareTop == tre.compareBottom) { + if (topGrain != null) topGrain.doJump(tre); + return; + } + + if (tre.compareTop != tre.compareBottom) { + if (bottomGrain != null) bottomGrain.doJump(tre); + return; + } + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneGoBackward.java b/src/net/tortuga/level/program/tiles/stones/StoneGoBackward.java new file mode 100644 index 0000000..d6c2bcb --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneGoBackward.java @@ -0,0 +1,61 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneGoBackward extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GO_BACK; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MOVEMENT; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + // FIXME implement GoBw + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneGoForward.java b/src/net/tortuga/level/program/tiles/stones/StoneGoForward.java new file mode 100644 index 0000000..b93c539 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneGoForward.java @@ -0,0 +1,61 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneGoForward extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GO_FORTH; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MOVEMENT; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + // FIXME implement GoFw + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneGoto.java b/src/net/tortuga/level/program/tiles/stones/StoneGoto.java new file mode 100644 index 0000000..022c638 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneGoto.java @@ -0,0 +1,62 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneGoto extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.GOTO; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.JUMPS; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) throws TurtleRuntimeException + { + topGrain.doJump(tre); + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneLabel.java b/src/net/tortuga/level/program/tiles/stones/StoneLabel.java new file mode 100644 index 0000000..eb468ed --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneLabel.java @@ -0,0 +1,80 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneLabel extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.LABEL; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.GRAIN_LABELS; + } + + + @Override + public CoordI getSubOverlayIndex() + { + return StoneTx.LABEL_FILL; + } + + + @Override + public RGB getSubOverlayColor() + { + return StoneColors.LABEL_COLORS[variant]; + } + + + @Override + public boolean isSingleInstance() + { + return true; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + {} +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneLookDown.java b/src/net/tortuga/level/program/tiles/stones/StoneLookDown.java new file mode 100644 index 0000000..9639006 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneLookDown.java @@ -0,0 +1,61 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneLookDown extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.LOOK_DOWN; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.TESTS; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + // FIXME implement LookDown + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneLookFront.java b/src/net/tortuga/level/program/tiles/stones/StoneLookFront.java new file mode 100644 index 0000000..3851d64 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneLookFront.java @@ -0,0 +1,61 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneLookFront extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.LOOK_FRONT; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.TESTS; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + // FIXME implement LookFront + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneLookInv.java b/src/net/tortuga/level/program/tiles/stones/StoneLookInv.java new file mode 100644 index 0000000..18693d0 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneLookInv.java @@ -0,0 +1,61 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneLookInv extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.LOOK_INV; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.TESTS; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + // FIXME implement Look Inv + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathAdd.java b/src/net/tortuga/level/program/tiles/stones/StoneMathAdd.java new file mode 100644 index 0000000..67c8830 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathAdd.java @@ -0,0 +1,76 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathAdd extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_ADD; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valIn; + int valOut; + + int outVar = bottomGrain.getVariant(); + + if (topGrain.getGrainType() == EGrainType.VAR) { + valIn = tre.getVariable(topGrain.getVariant()); + } else { + valIn = topGrain.getExtraNumber(); + } + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + tre.setVariable(outVar, valIn + valOut); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathAnd.java b/src/net/tortuga/level/program/tiles/stones/StoneMathAnd.java new file mode 100644 index 0000000..60bf281 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathAnd.java @@ -0,0 +1,79 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathAnd extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_AND; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valIn; + int valOut; + + int outVar = bottomGrain.getVariant(); + + if (topGrain.getGrainType() == EGrainType.VAR) { + valIn = tre.getVariable(topGrain.getVariant()); + } else { + valIn = topGrain.getExtraNumber(); + } + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + boolean bIn = valIn != 0; + boolean bOut = valOut != 0; + + tre.setVariable(outVar, (bIn && bOut) ? 1 : 0); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathDecJnz.java b/src/net/tortuga/level/program/tiles/stones/StoneMathDecJnz.java new file mode 100644 index 0000000..7a32d20 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathDecJnz.java @@ -0,0 +1,76 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathDecJnz extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_DEC_JNZ; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) throws TurtleRuntimeException + { + int valOut; + + int outVar = bottomGrain.getVariant(); + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + tre.setVariable(outVar, valOut - 1); + + if (valOut - 1 != 0) { + if (topGrain != null) { + topGrain.doJump(tre); + } + } + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathDecJz.java b/src/net/tortuga/level/program/tiles/stones/StoneMathDecJz.java new file mode 100644 index 0000000..6d32e83 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathDecJz.java @@ -0,0 +1,76 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathDecJz extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.LABEL; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_DEC_JZ; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) throws TurtleRuntimeException + { + int valOut; + + int outVar = bottomGrain.getVariant(); + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + tre.setVariable(outVar, valOut - 1); + + if (valOut - 1 == 0) { + if (topGrain != null) { + topGrain.doJump(tre); + } + } + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathDiv.java b/src/net/tortuga/level/program/tiles/stones/StoneMathDiv.java new file mode 100644 index 0000000..8d76763 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathDiv.java @@ -0,0 +1,76 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathDiv extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_DIV; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valIn; + int valOut; + + int outVar = bottomGrain.getVariant(); + + if (topGrain.getGrainType() == EGrainType.VAR) { + valIn = tre.getVariable(topGrain.getVariant()); + } else { + valIn = topGrain.getExtraNumber(); + } + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + tre.setVariable(outVar, Math.round(valOut / valIn)); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathInc.java b/src/net/tortuga/level/program/tiles/stones/StoneMathInc.java new file mode 100644 index 0000000..051c3af --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathInc.java @@ -0,0 +1,69 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathInc extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_INC; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valOut; + + int outVar = bottomGrain.getVariant(); + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + tre.setVariable(outVar, valOut + 1); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathMod.java b/src/net/tortuga/level/program/tiles/stones/StoneMathMod.java new file mode 100644 index 0000000..3140c2b --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathMod.java @@ -0,0 +1,76 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathMod extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_MOD; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valIn; + int valOut; + + int outVar = bottomGrain.getVariant(); + + if (topGrain.getGrainType() == EGrainType.VAR) { + valIn = tre.getVariable(topGrain.getVariant()); + } else { + valIn = topGrain.getExtraNumber(); + } + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + tre.setVariable(outVar, Math.round(valOut % valIn)); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathMul.java b/src/net/tortuga/level/program/tiles/stones/StoneMathMul.java new file mode 100644 index 0000000..748d6a9 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathMul.java @@ -0,0 +1,76 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathMul extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_MUL; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valIn; + int valOut; + + int outVar = bottomGrain.getVariant(); + + if (topGrain.getGrainType() == EGrainType.VAR) { + valIn = tre.getVariable(topGrain.getVariant()); + } else { + valIn = topGrain.getExtraNumber(); + } + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + tre.setVariable(outVar, valOut * valIn); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathNot.java b/src/net/tortuga/level/program/tiles/stones/StoneMathNot.java new file mode 100644 index 0000000..2471510 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathNot.java @@ -0,0 +1,71 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathNot extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_NOT; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valOut; + + int outVar = bottomGrain.getVariant(); + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + boolean bOut = valOut != 0; + + tre.setVariable(outVar, (!bOut) ? 1 : 0); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathOr.java b/src/net/tortuga/level/program/tiles/stones/StoneMathOr.java new file mode 100644 index 0000000..2583348 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathOr.java @@ -0,0 +1,79 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathOr extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_OR; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valIn; + int valOut; + + int outVar = bottomGrain.getVariant(); + + if (topGrain.getGrainType() == EGrainType.VAR) { + valIn = tre.getVariable(topGrain.getVariant()); + } else { + valIn = topGrain.getExtraNumber(); + } + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + boolean bIn = valIn != 0; + boolean bOut = valOut != 0; + + tre.setVariable(outVar, (bIn || bOut) ? 1 : 0); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathSet.java b/src/net/tortuga/level/program/tiles/stones/StoneMathSet.java new file mode 100644 index 0000000..3735ba9 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathSet.java @@ -0,0 +1,74 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathSet extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_SET; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valIn; + + int outVar = bottomGrain.getVariant(); + + if (topGrain.getGrainType() == EGrainType.VAR) { + valIn = tre.getVariable(topGrain.getVariant()); + } else { + valIn = topGrain.getExtraNumber(); + } + + outVar = bottomGrain.getVariant(); + + tre.setVariable(outVar, valIn); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathSub.java b/src/net/tortuga/level/program/tiles/stones/StoneMathSub.java new file mode 100644 index 0000000..0dc04d6 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathSub.java @@ -0,0 +1,76 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathSub extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_SUB; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valIn; + int valOut; + + int outVar = bottomGrain.getVariant(); + + if (topGrain.getGrainType() == EGrainType.VAR) { + valIn = tre.getVariable(topGrain.getVariant()); + } else { + valIn = topGrain.getExtraNumber(); + } + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + tre.setVariable(outVar, valOut - valIn); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneMathXor.java b/src/net/tortuga/level/program/tiles/stones/StoneMathXor.java new file mode 100644 index 0000000..0195d85 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneMathXor.java @@ -0,0 +1,79 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneMathXor extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return grainType == EGrainType.VAR; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.REQUIRED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.MATH_XOR; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MATH; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int valIn; + int valOut; + + int outVar = bottomGrain.getVariant(); + + if (topGrain.getGrainType() == EGrainType.VAR) { + valIn = tre.getVariable(topGrain.getVariant()); + } else { + valIn = topGrain.getExtraNumber(); + } + + valOut = tre.getVariable(bottomGrain.getVariant()); + outVar = bottomGrain.getVariant(); + + boolean bIn = valIn != 0; + boolean bOut = valOut != 0; + + tre.setVariable(outVar, (bIn != bOut) ? 1 : 0); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneReturn.java b/src/net/tortuga/level/program/tiles/stones/StoneReturn.java new file mode 100644 index 0000000..127d122 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneReturn.java @@ -0,0 +1,62 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.TurtleRuntimeException; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneReturn extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.RETURN; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.JUMPS; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) throws TurtleRuntimeException + { + tre.popAddress(); + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneSleep.java b/src/net/tortuga/level/program/tiles/stones/StoneSleep.java new file mode 100644 index 0000000..b208ebb --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneSleep.java @@ -0,0 +1,67 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneSleep extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return grainType == EGrainType.NUMBER || grainType == EGrainType.VAR; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.OPTIONAL; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.SLEEP; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.SPECIAL; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + int secs = 1; + + if (topGrain.getGrainType() == EGrainType.NUMBER) secs = topGrain.getExtraNumber(); + if (topGrain.getGrainType() == EGrainType.VAR) secs = tre.getVariable(topGrain.getVariant()); + + tre.addSleep(secs); + + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneTurnLeft.java b/src/net/tortuga/level/program/tiles/stones/StoneTurnLeft.java new file mode 100644 index 0000000..0149a28 --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneTurnLeft.java @@ -0,0 +1,61 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneTurnLeft extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.TURN_LEFT; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MOVEMENT; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + // FIXME implement TurnLeft + } +} diff --git a/src/net/tortuga/level/program/tiles/stones/StoneTurnRight.java b/src/net/tortuga/level/program/tiles/stones/StoneTurnRight.java new file mode 100644 index 0000000..88ce06f --- /dev/null +++ b/src/net/tortuga/level/program/tiles/stones/StoneTurnRight.java @@ -0,0 +1,61 @@ +package net.tortuga.level.program.tiles.stones; + + +import net.tortuga.level.TurtleController; +import net.tortuga.level.TurtleRuntimeEnvironment; +import net.tortuga.level.program.tiles.*; + +import com.porcupine.color.RGB; +import com.porcupine.coord.CoordI; + + +public class StoneTurnRight extends ProgTileStone { + + @Override + public boolean acceptsGrainTop(EGrainType grainType) + { + return false; + } + + + @Override + public boolean acceptsGrainBottom(EGrainType grainType) + { + return false; + } + + + @Override + public EGrainSlotMode getGrainModeTop() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public EGrainSlotMode getGrainModeBottom() + { + return EGrainSlotMode.UNUSED; + } + + + @Override + public CoordI getOverlayIndex() + { + return StoneTx.TURN_RIGHT; + } + + + @Override + public RGB getStoneColor() + { + return StoneColors.MOVEMENT; + } + + + @Override + public void execute(ProgTileGrain topGrain, ProgTileGrain bottomGrain, TurtleRuntimeEnvironment tre, TurtleController tc) + { + // FIXME implement TurnRight + } +} diff --git a/src/net/tortuga/sounds/AudioX.java b/src/net/tortuga/sounds/AudioX.java new file mode 100644 index 0000000..2c96218 --- /dev/null +++ b/src/net/tortuga/sounds/AudioX.java @@ -0,0 +1,331 @@ +package net.tortuga.sounds; + + +import net.tortuga.Constants; + +import org.newdawn.slick.openal.Audio; + +import com.porcupine.coord.Coord; +import com.porcupine.math.Calc; + + +/** + * Wrapper class for slick audio with shorter method names and support for + * Coords + * + * @author MightyPork + */ +public class AudioX implements Audio { + + private Audio audio = null; + private float pauseLoopPosition = 0; + private boolean looping = false; + private boolean isLoopPaused = false; + private boolean playingAsMusic = false; + private boolean playingAsEffect = false; + private float playPitch = 1; + private float playGain = 1; + + + /** + * Pause loop (remember position and stop playing) - if was looping + */ + public void pauseLoop() + { + if (isPlaying() && looping) { + pauseLoopPosition = audio.getPosition(); + stop(); + isLoopPaused = true; + } + } + + + /** + * Resume loop (if was paused) + * + * @return source ID + */ + public int resumeLoop() + { + int source = -1; + if (looping && isLoopPaused) { + if (playingAsMusic) { + source = audio.playAsMusic(playPitch, playGain, true); + } else if (playingAsEffect) { + source = audio.playAsSoundEffect(playPitch, playGain, true); + } + audio.setPosition(pauseLoopPosition); + isLoopPaused = false; + } + return source; + } + + + /** + * AudioX from slick Audio + * + * @param audio + */ + public AudioX(Audio audio) { + this.audio = audio; + } + + + @Override + public void stop() + { + audio.stop(); + //looping = false; + isLoopPaused = false; + } + + + @Override + public int getBufferID() + { + return audio.getBufferID(); + } + + + @Override + public boolean isPlaying() + { + return audio.isPlaying(); + } + + + @Override + public boolean isPaused() + { + return audio.isPaused(); + } + + + /** + * Play effect loop + * + * @param pitch effect pitch + * @param gain effect volume + * @return resource id + */ + public int playAsEffectLoop(float pitch, float gain) + { + return playEffectLoop(pitch, gain); + } + + + /** + * Play effect loop + * + * @param pitch effect pitch + * @param gain effect volume + * @return resource id + */ + public int playEffectLoop(float pitch, float gain) + { + playPitch = pitch; + playGain = gain; + looping = true; + playingAsEffect = true; + playingAsMusic = false; + return playAsSoundEffect(pitch, gain, true, SoundManager.listener); + } + + + @Override + public int playAsSoundEffect(float pitch, float gain, boolean loop) + { + playPitch = pitch; + playGain = gain; + looping = loop; + playingAsEffect = true; + playingAsMusic = false; + return playAsSoundEffect(pitch, gain, loop, SoundManager.listener); + } + + + @Override + public int playAsSoundEffect(float pitch, float gain, boolean loop, float x, float y, float z) + { + return audio.playAsSoundEffect(pitch, gain, loop, x, y, z); + } + + + /** + * Play this sound as a sound effect + * + * @param pitch The pitch of the play back + * @param gain The gain of the play back + * @param x coord x + * @param y coord y + * @param z coord z + * @return resource index + */ + public int playAsSoundEffect(float pitch, float gain, float x, float y, float z) + { + return playAsSoundEffect(pitch, gain, false, x, y, z); + } + + + /** + * Play this sound as a sound effect + * + * @param pitch The pitch of the play back + * @param gain The gain of the play back + * @param loop True if we should loop + * @param pos The position of the sound + * @return The ID of the source playing the sound + */ + public int playAsSoundEffect(float pitch, float gain, boolean loop, Coord pos) + { + return audio.playAsSoundEffect(pitch, gain, loop, (float) pos.x, (float) pos.y, (float) pos.z); + } + + + // shorter + /** + * Play this sound as a sound effect + * + * @param pitch The pitch of the play back + * @param gain The gain of the play back + * @param loop True if we should loop + * @return The ID of the source playing the sound + */ + public int playEffect(float pitch, float gain, boolean loop) + { + playPitch = pitch; + playGain = gain; + looping = loop; + playingAsEffect = true; + playingAsMusic = false; + return playAsSoundEffect(pitch, gain, loop, SoundManager.listener); + } + + + /** + * Play this sound as a sound effect + * + * @param pitch The pitch of the play back + * @param gain The gain of the play back + * @param loop True if we should loop + * @param x The x position of the sound + * @param y The y position of the sound + * @param z The z position of the sound + * @return The ID of the source playing the sound + */ + public int playEffect(float pitch, float gain, boolean loop, float x, float y, float z) + { + return playAsSoundEffect(pitch, gain, loop, x, y, z); + } + + + /** + * Play this sound as a sound effect + * + * @param pitch The pitch of the play back + * @param gain The gain of the play back + * @param loop True if we should loop + * @param pos The position of the sound + * @return The ID of the source playing the sound + */ + public int playEffect(float pitch, float gain, boolean loop, Coord pos) + { + return playAsSoundEffect(pitch, gain, loop, pos); + } + + + /** + * Play this sound as a sound effect with linear Z fading + * + * @param pitch The pitch of the play back + * @param gain The gain of the play back + * @param hearDist hearing distance + * @param loop True if we should loop + * @param pos The position of the sound + * @return The ID of the source playing the sound + */ + public int playEffectLinearZ(float pitch, float gain, float hearDist, boolean loop, Coord pos) + { + float gain2 = Calc.clampf((hearDist - pos.distTo(new Coord(0, 0, 0))) * (1 / hearDist), 0, 1) * gain; + return playAsSoundEffect(pitch, gain2, loop, new Coord(pos.x, Constants.LISTENER_POS.y - 2, Constants.LISTENER_POS.z)); + } + + + // longer + + @Override + public int playAsMusic(float pitch, float gain, boolean loop) + { + playPitch = pitch; + playGain = gain; + looping = loop; + playingAsEffect = false; + playingAsMusic = true; + return audio.playAsMusic(pitch, gain, loop); + } + + + //shorter + + /** + * Play this sound as music + * + * @param pitch The pitch of the play back + * @param gain The gain of the play back + * @param loop True if we should loop + * @return The ID of the source playing the sound + */ + public int playMusic(float pitch, float gain, boolean loop) + { + return playAsMusic(pitch, gain, loop); + } + + + /** + * Play this sound as music + * + * @param pitch The pitch of the play back + * @param gain The gain of the play back + * @return The ID of the source playing the sound + */ + public int playMusicLoop(float pitch, float gain) + { + return playAsMusic(pitch, gain, true); + } + + + /** + * Play this sound as music + * + * @param pitch The pitch of the play back + * @param gain The gain of the play back + * @return The ID of the source playing the sound + */ + public int playAsMusicLoop(float pitch, float gain) + { + return playAsMusic(pitch, gain, true); + } + + + @Override + public boolean setPosition(float position) + { + return audio.setPosition(position); + } + + + @Override + public float getPosition() + { + return audio.getPosition(); + } + + + @Override + public void release() + { + audio.release(); + audio = null; + } + +} diff --git a/src/net/tortuga/sounds/EffectPlayer.java b/src/net/tortuga/sounds/EffectPlayer.java new file mode 100644 index 0000000..3953e22 --- /dev/null +++ b/src/net/tortuga/sounds/EffectPlayer.java @@ -0,0 +1,52 @@ +package net.tortuga.sounds; + + +import com.porcupine.mutable.MFloat; + + +public class EffectPlayer { + + /** the track */ + private AudioX track; + + /** base gain for sfx */ + private double baseGain = 1; + + /** base pitch for sfx */ + private double basePitch = 1; + + /** dedicated volume control */ + private MFloat gainMultiplier = null; + + private float lastUpdateComputedGain = 0; + + + public EffectPlayer(AudioX track, double basePitch, double baseGain, MFloat gainMultiplier) { + this.track = track; + + this.baseGain = baseGain; + this.basePitch = basePitch; + + this.gainMultiplier = gainMultiplier; + } + + + public int play(double pitch, double gain) + { + if (track == null) return -1; + + double computedGain = gainMultiplier.get() * gain * baseGain; + double computedPitch = pitch * basePitch; + + return track.playEffect((float) computedPitch, (float) computedGain, false); + + } + + + public int play(double gain) + { + if (track == null) return -1; + return play(1, (float) gain); + } + +} diff --git a/src/net/tortuga/sounds/Effects.java b/src/net/tortuga/sounds/Effects.java new file mode 100644 index 0000000..d6c441e --- /dev/null +++ b/src/net/tortuga/sounds/Effects.java @@ -0,0 +1,102 @@ +package net.tortuga.sounds; + + +import java.util.HashMap; +import java.util.Map; + +import com.porcupine.mutable.MFloat; + + +/** + * Audio effect manager + * + * @author MightyPork + */ +public class Effects { + + private static Map efects = new HashMap(); + + + /** + * Init players + */ + public static void init() + { + MFloat gGui = SoundManager.volumeGui; + MFloat gEff = SoundManager.volumeEffects; + + efects.put("gui.button.menu", new EffectPlayer(SoundManager.click_typewriter1, 1f, 0.3f, gGui)); + efects.put("gui.button.dialog", new EffectPlayer(SoundManager.click_typewriter2, 1f, 0.6f, gGui)); + efects.put("gui.switch", new EffectPlayer(SoundManager.click_switch, 1f, 0.3f, gGui)); + efects.put("gui.default", new EffectPlayer(SoundManager.click_button1, 1.4f, 0.3f, gGui)); + efects.put("gui.popup", new EffectPlayer(SoundManager.gui_popup, 1, 0.55f, gGui)); + efects.put("gui.screenshot", new EffectPlayer(SoundManager.gui_shutter, 1f, 1f, gGui)); + + efects.put("gui.error", new EffectPlayer(SoundManager.gui_error, 1f, 0.7f, gGui)); + efects.put("gui.tray", new EffectPlayer(SoundManager.click_tray, 1f, 0.3f, gGui)); + efects.put("gui.open", new EffectPlayer(SoundManager.click_open, 1f, 0.3f, gGui)); + efects.put("gui.rattle", new EffectPlayer(SoundManager.click_rattle, 1f, 0.3f, gGui)); + efects.put("gui.gear1", new EffectPlayer(SoundManager.gui_gear1, 1f, 0.2f, gGui)); + efects.put("gui.gear2", new EffectPlayer(SoundManager.gui_gear2, 1f, 0.2f, gGui)); + + efects.put("gui.program.grab", new EffectPlayer(SoundManager.click_button2, 1.2f, 0.3f, gGui)); + efects.put("gui.program.drop", new EffectPlayer(SoundManager.click_button2, 0.8f, 0.3f, gGui)); + efects.put("gui.program.delete", new EffectPlayer(SoundManager.click_button2, 0.4f, 0.3f, gGui)); + + efects.put("turtle.bell", new EffectPlayer(SoundManager.turtle_bell, 1f, 0.7f, gEff)); + efects.put("turtle.eat", new EffectPlayer(SoundManager.turtle_eat, 1f, 0.7f, gEff)); + efects.put("block.drop", new EffectPlayer(SoundManager.crate_drop, 1f, 0.4f, gEff)); + efects.put("turtle.drop", new EffectPlayer(SoundManager.turtle_drop, 1f, 0.4f, gEff)); + efects.put("water.splash", new EffectPlayer(SoundManager.splash, 1f, 0.4f, gEff)); + efects.put("fruit.destroy", new EffectPlayer(SoundManager.break_fruit, 1f, 0.8f, gEff)); + efects.put("switch.toggle", new EffectPlayer(SoundManager.click_switch, 1f, 0.4f, gEff)); + + } + + + private static EffectPlayer getPlayer(String resource) + { + EffectPlayer pl = efects.get(resource); + if (pl == null) { + throw new IllegalArgumentException("Unknown sound effect: \"" + resource + "\""); + } + return pl; + } + + + /** + * Play a resource with default settings + * + * @param resource resource ID + */ + public static void play(String resource) + { + getPlayer(resource).play(1); + } + + + /** + * Play resource with changed gain + * + * @param resource resource ID + * @param gain gain + */ + public static void play(String resource, double gain) + { + getPlayer(resource).play(gain); + } + + + /** + * Play resource with changed pitch and gain + * + * @param resource resource ID + * @param pitch pitch + * @param gain gain + */ + public static void play(String resource, double pitch, double gain) + { + getPlayer(resource).play(pitch, gain); + } + +} diff --git a/src/net/tortuga/sounds/LoopPlayer.java b/src/net/tortuga/sounds/LoopPlayer.java new file mode 100644 index 0000000..304e253 --- /dev/null +++ b/src/net/tortuga/sounds/LoopPlayer.java @@ -0,0 +1,119 @@ +package net.tortuga.sounds; + + +import net.tortuga.util.AnimDouble; + +import org.lwjgl.openal.AL10; + +import com.porcupine.mutable.MFloat; + + +public class LoopPlayer { + + private int sourceID = -1; + + /** the track */ + private AudioX track; + + /** animator for fade in and fade out */ + private AnimDouble fadeAnim = new AnimDouble(0); + + /** max gain for track */ + private float fullGain = 1; + + /** dedicated volume control */ + private MFloat gainMultiplier = null; + + private float lastUpdateComputedGain = 0; + + /** flag that track is paused */ + private boolean paused = true; + + + public LoopPlayer(AudioX track, float pitch, float fullGain, MFloat gainMultiplier) { + this.track = track; + + this.fullGain = fullGain; + fadeAnim.setTo(0); + this.gainMultiplier = gainMultiplier; + + if (track != null) { + sourceID = track.playAsEffectLoop(1, 0); + track.pauseLoop(); + } + + paused = true; + } + + + public void pause() + { + if (track == null) return; + if (paused) { + //System.out.println("Can't pause, loop is already paused."); + return; + } + + track.pauseLoop(); + paused = true; + } + + + public boolean isPaused() + { + return paused; + } + + + public void resume() + { + if (track == null) return; + if (!paused) { + //System.out.println("Can't resume, loop is already playing."); + return; // playing + } + + sourceID = track.resumeLoop(); + paused = false; + } + + + public void update(double delta) + { + if (track == null) return; + if (paused) { + return; + } + fadeAnim.update(delta); + + float computedGain = (float) (gainMultiplier.get() * fullGain * fadeAnim.delta()); + if (!paused && computedGain != lastUpdateComputedGain) { + AL10.alSourcef(sourceID, AL10.AL_GAIN, computedGain); + lastUpdateComputedGain = computedGain; + } + + if (computedGain == 0 && !paused) pause(); + } + + +// public void setVolume(double volume) { +// fadeAnim.setTo(volume); +// } + + public void fadeIn(double secs) + { + if (track == null) return; + resume(); + fadeAnim.stopAnimation(); + fadeAnim.addValue(1 - fadeAnim.delta(), secs * (1 - fadeAnim.delta())); + } + + + public void fadeOut(double secs) + { + if (track == null) return; + fadeAnim.stopAnimation(); + fadeAnim.addValue(-fadeAnim.delta(), secs * (fadeAnim.delta())); + } + +} diff --git a/src/net/tortuga/sounds/Loops.java b/src/net/tortuga/sounds/Loops.java new file mode 100644 index 0000000..977d2ad --- /dev/null +++ b/src/net/tortuga/sounds/Loops.java @@ -0,0 +1,122 @@ +package net.tortuga.sounds; + + +import java.util.HashMap; +import java.util.Map; + +import net.tortuga.App; +import net.tortuga.EWeather; + + +/** + * Audio loop manager + * + * @author MightyPork + */ +public class Loops { + + private static boolean ready; + + private static Map loops = new HashMap(); + + + /** + * Init the loops + */ + public static void init() + { + loops.put("ambient.water", new LoopPlayer(SoundManager.amb_water, 1f, 0.05f, SoundManager.volumeAmbients)); + loops.put("weather.rain", new LoopPlayer(SoundManager.amb_rain, 1f, 0.2f, SoundManager.volumeAmbients)); + loops.put("ambient.wilderness", new LoopPlayer(SoundManager.amb_wilderness, 1f, 0.08f, SoundManager.volumeAmbients)); + + loops.put("block.move", new LoopPlayer(SoundManager.loop_crate_move, 1f, 0.13f, SoundManager.volumeEffects)); + loops.put("turtle.move", new LoopPlayer(SoundManager.loop_turtle_move, 1f, 0.12f, SoundManager.volumeEffects)); + + ready = true; + } + + + private static LoopPlayer getPlayer(String resource) + { + LoopPlayer pl = loops.get(resource); + if (pl == null) { + throw new IllegalArgumentException("Unknown sound loop: \"" + resource + "\""); + } + return pl; + } + + + /** + * Start playing a loop with fade-in + * + * @param resource resource ID + * @param fadeIn fade in time (secs) + */ + public static void start(String resource, double fadeIn) + { + getPlayer(resource).fadeIn(fadeIn); + } + + + /** + * Stop playing a loop with fade-out + * + * @param resource resource ID + * @param fadeOut fade out time (secs) + */ + public static void stop(String resource, double fadeOut) + { + getPlayer(resource).fadeOut(fadeOut); + } + + + /** + * Play in-game ambient loops + */ + public static void playIngame() + { + start("ambient.water", 1); + } + + + /** + * Play main menu loops + */ + public static void playMenu() + { + start("ambient.wilderness", 1); + // TODO stop intro + + stop("ambient.water", 2); + stop("turtle.move", 0.5); + stop("block.move", 0.5); + + if (App.weather == EWeather.RAIN) { + getPlayer("weather.rain").fadeIn(1); + } + } + + + /** + * Play intro effect + */ + public static void playIntro() + { + // TODO play intro + } + + + /** + * Update players (fading in/out) + * + * @param delta delta time + */ + public static void update(double delta) + { + if (!ready) return; + for (LoopPlayer player : loops.values()) { + player.update(delta); + } + } + +} diff --git a/src/net/tortuga/sounds/SoundManager.java b/src/net/tortuga/sounds/SoundManager.java new file mode 100644 index 0000000..ea65482 --- /dev/null +++ b/src/net/tortuga/sounds/SoundManager.java @@ -0,0 +1,188 @@ +package net.tortuga.sounds; + + +import java.nio.FloatBuffer; +import java.util.Random; + +import net.tortuga.Constants; +import net.tortuga.util.Log; + +import org.lwjgl.openal.AL10; +import org.newdawn.slick.openal.SoundStore; + +import com.porcupine.coord.Coord; +import com.porcupine.math.Calc.Buffers; +import com.porcupine.mutable.MFloat; + + +/** + * Preloaded sounds. + * + * @author MightyPork + */ +@SuppressWarnings({ "javadoc", "unused" }) +public class SoundManager { + + /** Volume of GUI buttons and clicks */ + public static MFloat volumeGui = new MFloat(1F); + + /** Volume of ingame sound effects */ + public static MFloat volumeEffects = new MFloat(1F); + + /** Volume of ambients (music & weather) */ + public static MFloat volumeAmbients = new MFloat(1F); + + /** Volume of water */ + public static MFloat volumeWater = new MFloat(1F); + + protected static AudioX click_button1; + protected static AudioX click_button2; + protected static AudioX click_typewriter1; + protected static AudioX click_typewriter2; + protected static AudioX click_switch; + protected static AudioX click_open; + protected static AudioX click_rattle; + protected static AudioX click_tray; + + protected static AudioX gui_popup; + protected static AudioX gui_shutter; + protected static AudioX gui_gear1; + protected static AudioX gui_gear2; + + protected static AudioX gui_error; + protected static AudioX turtle_bell; + protected static AudioX turtle_eat; + protected static AudioX splash; + + protected static AudioX amb_water; + protected static AudioX amb_rain; + protected static AudioX amb_wilderness; + + protected static AudioX loop_crate_move; + protected static AudioX loop_turtle_move; + protected static AudioX crate_drop; + protected static AudioX turtle_drop; + protected static AudioX break_fruit; + +/* protected static AudioX musIntro; + protected static AudioX musIngameLoop; + protected static AudioX musMenuLoop; + protected static AudioX musDesignerLoop;*/ + + private static final String DIR_EFFECTS = "res/sounds/effects/"; + private static final String DIR_MUSIC = "res/sounds/music/"; + private static final String DIR_LOOPS = "res/sounds/loops/"; + + public static SoundStore player = SoundStore.get(); + + + public static void loadForSplash() + { + gui_shutter = loadSound(DIR_EFFECTS + "shutter.ogg"); + } + + + /** + * Load sounds + */ + public static void load() + { + click_button1 = loadSound(DIR_EFFECTS + "click_button1.ogg"); + click_button2 = loadSound(DIR_EFFECTS + "click_button2.ogg"); + click_typewriter1 = loadSound(DIR_EFFECTS + "click_typewriter1.ogg"); + click_typewriter2 = loadSound(DIR_EFFECTS + "click_typewriter2.ogg"); + click_switch = loadSound(DIR_EFFECTS + "click_switch.ogg"); + click_open = loadSound(DIR_EFFECTS + "click_open.ogg"); + click_rattle = loadSound(DIR_EFFECTS + "click_rattle.ogg"); + click_tray = loadSound(DIR_EFFECTS + "click_tray.ogg"); + + gui_popup = loadSound(DIR_EFFECTS + "popup.ogg"); + gui_gear1 = loadSound(DIR_EFFECTS + "gear1.ogg"); + gui_gear2 = loadSound(DIR_EFFECTS + "gear2.ogg"); + + turtle_bell = loadSound(DIR_EFFECTS + "bell.ogg"); + gui_error = loadSound(DIR_EFFECTS + "error.ogg"); + turtle_eat = loadSound(DIR_EFFECTS + "eat.ogg"); + splash = loadSound(DIR_EFFECTS + "splash.ogg"); + + amb_water = loadSound(DIR_LOOPS + "stream.ogg"); + amb_rain = loadSound(DIR_LOOPS + "rain.ogg"); + amb_wilderness = loadSound(DIR_LOOPS + "wilderness.ogg"); + + loop_crate_move = loadSound(DIR_LOOPS + "crate_move.ogg"); + loop_turtle_move = loadSound(DIR_LOOPS + "turtle_walking.ogg"); + crate_drop = loadSound(DIR_EFFECTS + "block_drop.ogg"); + turtle_drop = loadSound(DIR_EFFECTS + "turtle_drop.ogg"); + break_fruit = loadSound(DIR_EFFECTS + "break_fruit.ogg"); + + Loops.init(); + Effects.init(); + } + + public static Coord listener = new Coord(); + private static Random rand = new Random(); + + + /** + * Set listener pos + * + * @param pos + */ + public static void setListener(Coord pos) + { + listener.setTo(pos); + FloatBuffer buf3 = Buffers.alloc(3); + FloatBuffer buf6 = Buffers.alloc(6); + buf3.clear(); + Buffers.fill(buf3, (float) pos.x, (float) pos.y, (float) pos.z); + AL10.alListener(AL10.AL_POSITION, buf3); + + buf3.clear(); + Buffers.fill(buf3, 0, 0, 0); + AL10.alListener(AL10.AL_VELOCITY, buf3); + + buf6.clear(); + Buffers.fill(buf6, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f); + AL10.alListener(AL10.AL_ORIENTATION, buf6); + + buf3 = buf6 = null; + } + + + /** + * load one sound + * + * @param path file path + * @return the sound + */ + private static AudioX loadSound(String path) + { + try { + String ext = path.substring(path.length() - 3).toLowerCase(); + AudioX audio = null; + if (ext.equals("ogg")) { + audio = new AudioX(player.getOgg(path)); + } + if (ext.equals("wav")) { + audio = new AudioX(player.getWAV(path)); + } + if (ext.equals("aif")) { + audio = new AudioX(player.getAIF(path)); + } + if (ext.equals("mod")) { + audio = new AudioX(player.getMOD(path)); + } + if (Constants.LOG_SOUNDS) Log.f2("Sound " + path + " loaded."); + return audio; + } catch (Exception e) { + Log.e("ERROR WHILE LOADING: " + path); + throw new RuntimeException(e); + } + } + + + public static void update(double delta) + { + Loops.update(delta); + } +} diff --git a/src/net/tortuga/textures/TextureManager.java b/src/net/tortuga/textures/TextureManager.java new file mode 100644 index 0000000..6443900 --- /dev/null +++ b/src/net/tortuga/textures/TextureManager.java @@ -0,0 +1,80 @@ +package net.tortuga.textures; + + +import static org.lwjgl.opengl.GL11.*; + +import java.io.IOException; + +import net.tortuga.Constants; +import net.tortuga.util.Log; + +import org.newdawn.slick.opengl.Texture; +import org.newdawn.slick.opengl.TextureLoader; +import org.newdawn.slick.util.ResourceLoader; + + +/** + * Texture manager + * + * @author MightyPork + */ +public class TextureManager { + + private static final boolean DEBUG = Constants.LOG_TEXTURES; + private static Texture lastBinded = null; + + + /** + * Load texture + * + * @param resourcePath + * @return the loaded texture + */ + public static Texture load(String resourcePath) + { + try { + String ext = resourcePath.substring(resourcePath.length() - 4); + + Texture texture = TextureLoader.getTexture(ext.toUpperCase(), ResourceLoader.getResourceAsStream(resourcePath)); + + if (texture != null) { + if (DEBUG) Log.f2("Texture " + resourcePath + " loaded."); + + return texture; + } + + Log.w("Texture " + resourcePath + " could not be loaded."); + return null; + } catch (IOException e) { + Log.e("Loading of texture " + resourcePath + " failed.", e); + throw new RuntimeException(e); + } + + } + + + /** + * Bind texture + * + * @param texture the texture + * @throws RuntimeException if not loaded yet + */ + public static void bind(Texture texture) throws RuntimeException + { + if (texture != lastBinded) { + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D, texture.getTextureID()); + lastBinded = texture; + } + } + + + /** + * Unbind all + */ + public static void unbind() + { + glBindTexture(GL_TEXTURE_2D, 0); + lastBinded = null; + } +} diff --git a/src/net/tortuga/textures/Textures.java b/src/net/tortuga/textures/Textures.java new file mode 100644 index 0000000..b030478 --- /dev/null +++ b/src/net/tortuga/textures/Textures.java @@ -0,0 +1,145 @@ +package net.tortuga.textures; + + +import static org.lwjgl.opengl.GL11.*; +import net.tortuga.level.map.entities.EntityTx; +import net.tortuga.level.map.tiles.MapTx; + +import org.newdawn.slick.opengl.Texture; + + +/** + * Texture loading class + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +// unrelevant +public class Textures { + + protected static Texture buttons_menu; + protected static Texture buttons_small; + + // patterns need raw access (are repeated) + public static Texture steel_small_dk; + public static Texture steel_small_lt; + public static Texture steel_big_dk; + public static Texture steel_big_lt; + public static Texture steel_big_scratched; + public static Texture steel_brushed; + public static Texture steel_rivet_belts; + public static Texture water; + + // particles need direct access + public static Texture snow; + public static Texture leaves; + public static Texture rain; + public static Texture circle; + + protected static Texture logo; + protected static Texture widgets; + public static Texture program; + protected static Texture icons; + + public static Texture map_tiles; + public static Texture map_shading; + + // spotted + public static Texture turtle_blue; + public static Texture turtle_green; + public static Texture turtle_purple; + public static Texture turtle_yellow; + + // misc + public static Texture turtle_red; + public static Texture turtle_brown_dk; + public static Texture turtle_brown_lt; + public static Texture turtle_brown_orn; + // a shadow sheet + public static Texture turtle_shadows; + + public static Texture food; + public static Texture food_shadows; + + private static final String GUI = "res/images/gui/"; + private static final String PATTERNS = "res/images/patterns/"; + private static final String PROGRAM = "res/images/program/"; + private static final String TILES = "res/images/tiles/"; + private static final String SPRITES = "res/images/sprites/"; + private static final String PARTICLES = "res/images/particles/"; + + + /** + * Load what's needed for splash + */ + public static void loadForSplash() + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + logo = TextureManager.load(GUI + "logo.png"); + steel_small_dk = TextureManager.load(PATTERNS + "steel.png"); + + Tx.initForSplash(); + } + + + /** + * Load textures + */ + public static void load() + { + // gui + buttons_menu = TextureManager.load(GUI + "buttons_menu.png"); + buttons_small = TextureManager.load(GUI + "buttons_small.png"); + widgets = TextureManager.load(GUI + "widgets.png"); + icons = TextureManager.load(GUI + "icons.png"); + + // patterns + + steel_small_lt = TextureManager.load(PATTERNS + "steel_light.png"); // XXX Unused texture + steel_big_dk = TextureManager.load(PATTERNS + "steel_big.png"); + steel_big_lt = TextureManager.load(PATTERNS + "steel_big_light.png"); + steel_big_scratched = TextureManager.load(PATTERNS + "steel_big_scratched.png"); + steel_brushed = TextureManager.load(PATTERNS + "steel_brushed.png"); + steel_rivet_belts = TextureManager.load(PATTERNS + "rivet_belts.png"); + water = TextureManager.load(PATTERNS + "water.png"); + + // particles + snow = TextureManager.load(PARTICLES + "snow.png"); + leaves = TextureManager.load(PARTICLES + "leaves.png"); + rain = TextureManager.load(PARTICLES + "rain.png"); + circle = TextureManager.load(PARTICLES + "circle.png"); + + // program tiles + program = TextureManager.load(PROGRAM + "program.png"); + + // map tiles + map_tiles = TextureManager.load(TILES + "tiles.png"); + map_shading = TextureManager.load(TILES + "shading.png"); + + // turtle + turtle_blue = TextureManager.load(SPRITES + "HD-blue.png"); + turtle_yellow = TextureManager.load(SPRITES + "HD-yellow.png"); + turtle_green = TextureManager.load(SPRITES + "HD-green.png"); + turtle_purple = TextureManager.load(SPRITES + "HD-purple.png"); + turtle_red = TextureManager.load(SPRITES + "HD-red.png"); + + turtle_brown_dk = TextureManager.load(SPRITES + "HD-brown-dk.png"); + turtle_brown_lt = TextureManager.load(SPRITES + "HD-brown-lt.png"); + turtle_brown_orn = TextureManager.load(SPRITES + "HD-brown-ornamental.png"); + turtle_shadows = TextureManager.load(SPRITES + "HD-shadow.png"); + + food = TextureManager.load(SPRITES + "food.png"); + food_shadows = TextureManager.load(SPRITES + "food-shadow.png"); + + glDisable(GL_TEXTURE_2D); + + Tx.init(); + MapTx.init(); + EntityTx.init(); + } + +} diff --git a/src/net/tortuga/textures/Tx.java b/src/net/tortuga/textures/Tx.java new file mode 100644 index 0000000..a5df8d3 --- /dev/null +++ b/src/net/tortuga/textures/Tx.java @@ -0,0 +1,127 @@ +package net.tortuga.textures; + + +/** + * List of texture quads for GUIs + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class Tx { + + // logo + public static TxQuad LOGO; + public static TxQuad TITLE; + + // buttons_small, buttons_menu + public static TxQuad BTN_SMALL; + public static TxQuad BTN_MENU; + + // screw + public static TxQuad SCREW; + + // checkbox + public static TxQuad CKBOX_ON; + public static TxQuad CKBOX_OFF; + + // radio button + public static TxQuad RADIO_ON; + public static TxQuad RADIO_OFF; + + // slider H + public static TxQuad SLIDER_TRACK; + public static TxQuad SLIDER_FILL; + public static TxQuad SLIDER_FILL2; + public static TxQuad SLIDER_HANDLE; + + // scrollbar H + public static TxQuad SCROLL_H_TRACK; + public static TxQuad SCROLL_H_HANDLE; + public static TxQuad SCROLL_H_HANDLE_HOVER; + public static TxQuad SCROLL_H_DOTS; + public static TxQuad SCROLL_H_DOTS_HOVER; + + // scrollbar V + public static TxQuad SCROLL_V_TRACK; + public static TxQuad SCROLL_V_HANDLE; + public static TxQuad SCROLL_V_HANDLE_HOVER; + public static TxQuad SCROLL_V_DOTS; + public static TxQuad SCROLL_V_DOTS_HOVER; + + // icons + public static TxQuad ICON_QUIT; + public static TxQuad ICON_CODE; + public static TxQuad ICON_TURTLE; + + // slots + public static TxQuad SLOT_SQUARE; + public static TxQuad SLOT_HEXAGON; + public static TxQuad SLOT_WARNING; + + public static TxQuad STONE_SQUARE; + public static TxQuad STONE_HEXAGON; + + public static TxQuad WATER_CIRCLE; + + + public static void initForSplash() + { + // splash logo + LOGO = TxQuad.fromSize(Textures.logo, 15, 9, 226, 132); + } + + + public static void init() + { + // title image (word art) + TITLE = TxQuad.fromSize(Textures.logo, 0, 142, 256, 64); + + // buttons + BTN_SMALL = TxQuad.fromSize(Textures.buttons_small, 0, 0, 256, 72); + BTN_MENU = TxQuad.fromSize(Textures.buttons_menu, 0, 0, 256, 72); + + // screw + SCREW = TxQuad.fromSize(Textures.widgets, 25, 240, 16, 16); + + // checkbox + CKBOX_ON = TxQuad.fromSize(Textures.widgets, 0, 157, 58, 28); + CKBOX_OFF = TxQuad.fromSize(Textures.widgets, 0, 186, 58, 28); + + // radio button + RADIO_ON = TxQuad.fromSize(Textures.widgets, 59, 157, 22, 22); + RADIO_OFF = TxQuad.fromSize(Textures.widgets, 59, 157, 22, 22); + + // scrollbar H + SCROLL_H_TRACK = TxQuad.fromSize(Textures.widgets, 0, 26, 68, 8); + SCROLL_H_HANDLE = TxQuad.fromSize(Textures.widgets, 0, 35, 68, 19); + SCROLL_H_HANDLE_HOVER = TxQuad.fromSize(Textures.widgets, 0, 55, 68, 19); + SCROLL_H_DOTS = TxQuad.fromSize(Textures.widgets, 69, 35, 25, 19); + SCROLL_H_DOTS_HOVER = TxQuad.fromSize(Textures.widgets, 69, 55, 25, 19); + + // scrollbar V + SCROLL_V_TRACK = TxQuad.fromSize(Textures.widgets, 208, 0, 8, 68); + SCROLL_V_HANDLE = TxQuad.fromSize(Textures.widgets, 217, 0, 19, 68); + SCROLL_V_HANDLE_HOVER = TxQuad.fromSize(Textures.widgets, 237, 0, 19, 68); + SCROLL_V_DOTS = TxQuad.fromSize(Textures.widgets, 217, 69, 19, 25); + SCROLL_V_DOTS_HOVER = TxQuad.fromSize(Textures.widgets, 237, 69, 19, 25); + + SLIDER_TRACK = TxQuad.fromSize(Textures.widgets, 0, 0, 68, 14); + SLIDER_FILL = TxQuad.fromSize(Textures.widgets, 0, 15, 68, 10); + SLIDER_FILL2 = TxQuad.fromSize(Textures.widgets, 6, 15, 53, 10); + SLIDER_HANDLE = TxQuad.fromSize(Textures.widgets, 0, 232, 24, 24); + + ICON_QUIT = TxQuad.fromSize(Textures.icons, 0, 0, 32, 32); + ICON_CODE = TxQuad.fromSize(Textures.icons, 32, 0, 32, 32); + ICON_TURTLE = TxQuad.fromSize(Textures.icons, 64, 0, 32, 32); + + SLOT_SQUARE = TxQuad.fromSize(Textures.widgets, 192, 192, 64, 64); + SLOT_HEXAGON = TxQuad.fromSize(Textures.widgets, 127, 192, 64, 64); + SLOT_WARNING = TxQuad.fromSize(Textures.widgets, 127, 62, 64, 64); + + STONE_SQUARE = TxQuad.fromSize(Textures.widgets, 192, 127, 64, 64); + STONE_HEXAGON = TxQuad.fromSize(Textures.widgets, 127, 127, 64, 64); + + WATER_CIRCLE = TxQuad.fromSize(Textures.circle, 0, 0, 256, 256); + } + +} diff --git a/src/net/tortuga/textures/TxQuad.java b/src/net/tortuga/textures/TxQuad.java new file mode 100644 index 0000000..7d36ee4 --- /dev/null +++ b/src/net/tortuga/textures/TxQuad.java @@ -0,0 +1,72 @@ +package net.tortuga.textures; + + +import org.newdawn.slick.opengl.Texture; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Texture Quad (describing a part of a texture) + * + * @author MightyPork + */ +public class TxQuad { + + /** The texture */ + public Texture tx; + /** Coords in texture (pixels) */ + public Rect uvs; + /** Quad size */ + public Coord size; + + + /** + * Create TxQuad from left top coord and rect size + * + * @param tx texture + * @param x1 left top X + * @param y1 left top Y + * @param width area width + * @param height area height + * @return new TxQuad + */ + public static TxQuad fromSize(Texture tx, int x1, int y1, int width, int height) + { + return new TxQuad(tx, x1, y1, x1 + width, y1 + height); + } + + + /** + * @param tx Texture + * @param uvs Rect of texturwe UVs (pixels - from left top) + */ + public TxQuad(Texture tx, Rect uvs) { + this.tx = tx; + this.uvs = uvs.copy(); + this.size = uvs.getSize(); + } + + + /** + * Make of coords + * + * @param tx texture + * @param x1 x1 + * @param y1 y1 + * @param x2 x2 + * @param y2 y2 + */ + public TxQuad(Texture tx, int x1, int y1, int x2, int y2) { + this.tx = tx; + this.uvs = new Rect(x1, y1, x2, y2); + this.size = uvs.getSize(); + } + + + public TxQuad copy() + { + return new TxQuad(tx, uvs); + } +} diff --git a/src/net/tortuga/threads/EThreadStatus.java b/src/net/tortuga/threads/EThreadStatus.java new file mode 100644 index 0000000..0035f94 --- /dev/null +++ b/src/net/tortuga/threads/EThreadStatus.java @@ -0,0 +1,12 @@ +package net.tortuga.threads; + + +/** + * Loading thread status enumeration + * + * @author MightyPork + */ +public enum EThreadStatus +{ + UNSTARTED, SUCCESS, FAILURE, WORKING; +} diff --git a/src/net/tortuga/threads/ThreadSaveScreenshot.java b/src/net/tortuga/threads/ThreadSaveScreenshot.java new file mode 100644 index 0000000..dec1559 --- /dev/null +++ b/src/net/tortuga/threads/ThreadSaveScreenshot.java @@ -0,0 +1,96 @@ +package net.tortuga.threads; + + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.imageio.ImageIO; + +import net.tortuga.Constants; +import net.tortuga.util.Log; +import net.tortuga.util.Utils; + + +/** + * Thread for saving screenshot + * + * @author MightyPork + */ +public class ThreadSaveScreenshot extends Thread { + + private ByteBuffer buffer; + private int width; + private int height; + private int bpp; + + + /** + * Save screenshot thread + * + * @param buffer byte buffer with image data + * @param width screen width + * @param height screen height + * @param bpp bits per pixel + */ + public ThreadSaveScreenshot(ByteBuffer buffer, int width, int height, int bpp) { + this.buffer = buffer; + this.width = width; + this.height = height; + this.bpp = bpp; + } + + + private String getUniqueScreenshotName() + { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); + return df.format(new Date()); + } + + + @Override + public void run() + { + File screenshotDir = Utils.getGameSubfolder(Constants.DIR_SCREENSHOTS); + + screenshotDir.mkdirs(); + + String fname = getUniqueScreenshotName(); + + // generate unique filename + File file; + int index = 0; + while (true) { + file = new File(screenshotDir, fname + (index > 0 ? "-" + index : "") + ".png"); + if (!file.exists()) break; + index++; + } + + Log.f3("Saving screenshot to file: " + file); + + String format = "PNG"; + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + // convert to a buffered image + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int i = (x + (width * y)) * bpp; + int r = buffer.get(i) & 0xFF; + int g = buffer.get(i + 1) & 0xFF; + int b = buffer.get(i + 2) & 0xFF; + image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); + } + } + + // save to disk + try { + ImageIO.write(image, format, file); + } catch (IOException e) { + Log.e("Failed to save screenshot.", e); + } + } +} diff --git a/src/net/tortuga/util/Align.java b/src/net/tortuga/util/Align.java new file mode 100644 index 0000000..0c6826f --- /dev/null +++ b/src/net/tortuga/util/Align.java @@ -0,0 +1,21 @@ +package net.tortuga.util; + + +/** + * Alignment + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class Align { + + public static final int LEFT = -1; + public static final int RIGHT = 1; + public static final int TOP = 1; + public static final int BOTTOM = -1; + public static final int UP = 1; + public static final int DOWN = -1; + public static final int CENTER = 0; + public static final int MIDDLE = 0; + +} diff --git a/src/net/tortuga/util/AnimDouble.java b/src/net/tortuga/util/AnimDouble.java new file mode 100644 index 0000000..bfbceec --- /dev/null +++ b/src/net/tortuga/util/AnimDouble.java @@ -0,0 +1,222 @@ +package net.tortuga.util; + + +import com.porcupine.math.Calc; + + +/** + * Double which supports delta timing + * + * @author MightyPork + */ +public class AnimDouble { + + /** target double */ + public double end = 0; + + /** last tick double */ + public double start = 0; + + /** how long the transition should last */ + public double time = 1; + + /** current anim time */ + public double animTime = 0; + + + /** + * new DeltaDouble + * + * @param d value + */ + public AnimDouble(double d) { + this.start = this.end = d; + } + + + /** + * Get start double + * + * @return number + */ + public double getValue() + { + return start; + } + + + /** + * Get how much of the animation is already finished + * + * @return completion ratio + */ + public double getRatio() + { + if (time == 0) return 1; + return animTime / time; + } + + + /** + * Set to a value (both last and future) + * + * @param d + * @return this + */ + public AnimDouble setTo(double d) + { + start = end = d; + animTime = 0; + return this; + } + + + /** + * Copy other + * + * @param d + */ + public void setTo(AnimDouble d) + { + this.start = d.start; + this.end = d.end; + this.time = d.time; + this.animTime = d.animTime; + } + + + /** + * Increment animation on render + * + * @param delta delta + */ + public void update(double delta) + { + animTime = Calc.clampd(animTime + delta, 0, time); + if (isFinished()) { + time = 0; + animTime = 0; + start = end; + } + } + + + /** + * Get value at delta time
+ * render(delta) MUST BE CALLED FIRST! + * + * @return the value + */ + public double delta() + { + if (time == 0) return end; + return Calc.interpolate(start, end, animTime / time); + } + + + /** + * Get if animation is finished + * + * @return is finished + */ + public boolean isFinished() + { + return animTime >= time; + } + + + /** + * Add, start animating. + * + * @param num num + * @param time animation time (secs) + */ + public void addValue(double num, double time) + { + start = end; + end += num; + this.time = time; + animTime = 0; + } + + + /** + * Animate between two states in time + * + * @param from intial state + * @param to target state + * @param time animation time (secs) + */ + public void anim(double from, double to, double time) + { + setTo(from); + addValue(to - from, time); + } + + + /** + * Animate 0 to 1 + * + * @param time animation time (secs) + */ + public void animIn(double time) + { + setTo(0); + addValue(1, time); + } + + + /** + * Animate 1 to 0 + * + * @param time animation time (secs) + */ + public void animOut(double time) + { + setTo(1); + addValue(-1, time); + } + + + /** + * Make a copy + * + * @return copy + */ + public AnimDouble copy() + { + return new AnimDouble(end); + } + + + @Override + public String toString() + { + return end + ""; + } + + + /** + * Set to zero and stop animation + * + * @return this + */ + public AnimDouble clear() + { + start = end = 0; + time = 1; + animTime = 0; + return this; + } + + + /** + * Stop animation, keep current value + */ + public void stopAnimation() + { + start = end = delta(); + animTime = 0; + time = 1; + } +} diff --git a/src/net/tortuga/util/AnimDoubleDeg.java b/src/net/tortuga/util/AnimDoubleDeg.java new file mode 100644 index 0000000..ce2d319 --- /dev/null +++ b/src/net/tortuga/util/AnimDoubleDeg.java @@ -0,0 +1,35 @@ +package net.tortuga.util; + + +import com.porcupine.math.Calc; + + +/** + * Double which supports delta timing + * + * @author MightyPork + */ +public class AnimDoubleDeg extends AnimDouble { + + /** + * new AnimDoubleDeg + * + * @param d value + */ + public AnimDoubleDeg(double d) { + super(d); + } + + + /** + * Get value at delta time
+ * render(delta) MUST BE CALLED FIRST! + * + * @return the value + */ + @Override + public double delta() + { + return Calc.interpolateDeg(start, end, animTime / time); + } +} diff --git a/src/net/tortuga/util/CoordUtils.java b/src/net/tortuga/util/CoordUtils.java new file mode 100644 index 0000000..bdaee04 --- /dev/null +++ b/src/net/tortuga/util/CoordUtils.java @@ -0,0 +1,76 @@ +package net.tortuga.util; + + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Vec; +import com.porcupine.math.Calc.Deg; +import com.porcupine.math.Polar; + + +/** + * Functions for advanced coordinate manipulation. + * + * @author MightyPork + */ +public class CoordUtils { + + /** + * Get absolute coordinate of a point in local axis system, rotated from the + * standard one by ANGLE around Y axis. + * + * @param rotAngle angle of rotation + * @param theCoord the relative coordinate to transform + * @return absolute position of the transformed coordinate + */ + public static Coord coordToLocalSystem(double rotAngle, Coord theCoord) + { + Vec[] axes = getLocalAxes(rotAngle); + return axes[0].scale(theCoord.x).add(axes[1].scale(theCoord.z)).add(0, theCoord.y, 0); + } + + + /** + * Get "Z" axis vector of Cartesian coordinate system rotated by angle in + * degrees + * + * @param rotAngle angle in degrees + * @return Z axis unit vector + */ + public static Vec getLocalAxisZ(double rotAngle) + { + Polar p = new Polar(Deg.toRad(rotAngle + 90), 1); + Coord c = p.toCoord(); + return new Vec(c.x, 0, c.y); + } + + + /** + * Get "X" axis vector of Cartesian coordinate system rotated by angle in + * degrees + * + * @param rotAngle angle in degrees + * @return X axis unit vector + */ + public static Vec getLocalAxisX(double rotAngle) + { + Vec fw = getLocalAxisZ(rotAngle); + Polar pr = Polar.fromCoord(fw.x, fw.z); + pr.angle += Math.PI / 2; + Coord c = pr.toCoord(); + return new Vec(-c.x, 0, -c.y); + } + + + /** + * Get "X" and "Z" axis vectors of Cartesian coordinate system rotated by + * angle in degrees + * + * @param rotAngle angle in degrees + * @return local axes {X,Z} - {1,0,0} and {0,0,1} if angle is 0 + */ + public static Vec[] getLocalAxes(double rotAngle) + { + return new Vec[] { getLocalAxisX(rotAngle), getLocalAxisZ(rotAngle) }; + } + +} diff --git a/src/net/tortuga/util/DeltaDouble.java b/src/net/tortuga/util/DeltaDouble.java new file mode 100644 index 0000000..8c98926 --- /dev/null +++ b/src/net/tortuga/util/DeltaDouble.java @@ -0,0 +1,131 @@ +//package net.tortuga.util; +// +// +//import com.porcupine.math.Calc; +// +// +///** +// * Double which supports delta timing +// * +// * @author MightyPork +// */ +//public class DeltaDouble { +// /** target double */ +// public double d = 0; +// /** last tick double */ +// public double dlast = 0; +// +// /** +// * new DeltaDouble +// * +// * @param d value +// */ +// public DeltaDouble(double d) { +// this.dlast = this.d = d; +// } +// +// /** +// * Get target double +// * +// * @return number +// */ +// public double get() { +// return d; +// } +// +// /** +// * Set to a value (both last and future) +// * +// * @param d +// */ +// public void set(double d) { +// this.dlast = d; +// this.d = d; +// } +// +// /** +// * Copy other +// * +// * @param d +// */ +// public void set(DeltaDouble d) { +// this.dlast = d.dlast; +// this.d = d.d; +// } +// +// /** +// * Store value for delta timing +// */ +// public void pushLast() { +// dlast = d; +// } +// +// /** +// * Get value at delta time +// * +// * @param dtime time +// * @return interpolated value +// */ +// public double delta(double dtime) { +// return Calc.interpolate(dlast, d, dtime); +// } +// +// /** +// * Add +// * +// * @param num num +// */ +// public void add(double num) { +// d += num; +// } +// +// /** +// * Subtract +// * +// * @param num num +// */ +// public void sub(double num) { +// d -= num; +// } +// +// /** +// * multiply +// * +// * @param num num +// */ +// public void mul(double num) { +// d *= num; +// } +// +// /** +// * Divide +// * +// * @param num num +// */ +// public void div(double num) { +// d /= num; +// } +// +// /** +// * Clamp to range +// * +// * @param min min +// * @param max max +// */ +// public void clamp(double min, double max) { +// d = Calc.clampd(d, min, max); +// } +// +// /** +// * Make a copy +// * @return copy +// */ +// public DeltaDouble copy() { +// return new DeltaDouble(d); +// } +// +// @Override +// public String toString() { +// return d+""; +// } +//} diff --git a/src/net/tortuga/util/DeltaDoubleDeg.java b/src/net/tortuga/util/DeltaDoubleDeg.java new file mode 100644 index 0000000..7468c33 --- /dev/null +++ b/src/net/tortuga/util/DeltaDoubleDeg.java @@ -0,0 +1,26 @@ +//package net.tortuga.util; +// +// +//import com.porcupine.math.Calc; +// +// +///** +// * Delta double for angles in degrees +// * +// * @author MightyPork +// */ +//public class DeltaDoubleDeg extends DeltaDouble { +// /** +// * Delta double for angle +// * +// * @param d angle +// */ +// public DeltaDoubleDeg(double d) { +// super(d); +// } +// +// @Override +// public double delta(double dtime) { +// return Calc.interpolateDeg(dlast, d, dtime); +// } +//} diff --git a/src/net/tortuga/util/IonCoordI.java b/src/net/tortuga/util/IonCoordI.java new file mode 100644 index 0000000..172e198 --- /dev/null +++ b/src/net/tortuga/util/IonCoordI.java @@ -0,0 +1,99 @@ +package net.tortuga.util; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.tortuga.CustomIonMarks; + +import com.porcupine.coord.CoordI; +import com.porcupine.ion.Ion; +import com.porcupine.ion.Ionizable; + + +/** + * Ionizable Integer Coord + * + * @author MightyPork + */ +public class IonCoordI extends CoordI implements Ionizable { + + /** + * Ionizable Integer Coord + * + * @param other copied coord + */ + public IonCoordI(CoordI other) { + super(other); + } + + + /** + * Ionizable Integer Coord (empty constructor) + */ + public IonCoordI() { + super(); + } + + + /** + * Ionizable Integer Coord + * + * @param x X coord + * @param y Y coord + */ + public IonCoordI(int x, int y) { + super(x, y); + } + + + /** + * Ionizable Integer Coord + * + * @param x X coord + * @param y Y coord + * @param z Z coord + */ + public IonCoordI(int x, int y, int z) { + super(x, y, z); + } + + + @Override + public void ionRead(InputStream in) throws IOException + { + x = (Integer) Ion.fromStream(in); + y = (Integer) Ion.fromStream(in); + z = (Integer) Ion.fromStream(in); + } + + + @Override + public void ionWrite(OutputStream out) throws IOException + { + Ion.toStream(out, x); + Ion.toStream(out, y); + Ion.toStream(out, z); + } + + + @Override + public byte ionMark() + { + return CustomIonMarks.COORD; + } + + + /** + * Get copy + * + * @return copy + */ + @Override + public IonCoordI copy() + { + return new IonCoordI(x, y, z); + } + +} diff --git a/src/net/tortuga/util/Log.java b/src/net/tortuga/util/Log.java new file mode 100644 index 0000000..4c1f015 --- /dev/null +++ b/src/net/tortuga/util/Log.java @@ -0,0 +1,372 @@ +package net.tortuga.util; + + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import net.tortuga.Constants; + + +/** + * Sector static logger class. + * + * @author MightyPork + * @copy (c) 2012 + */ +public class Log { + + /** + * Global PowerCraft's logger. + */ + private static final Logger logger = Logger.getLogger("Sector"); + private static final Logger loggerError = Logger.getLogger("SectorErr"); + /** Logging enabled */ + public static boolean loggingEnabled = true; + /** Stdout printing enabled */ + public static boolean printToStdout = false; + + static { + try { + FileHandler handler1 = new FileHandler(Utils.getGameSubfolder(Constants.FILE_LOG).getPath()); + handler1.setFormatter(new LogFormatter(true)); + logger.addHandler(handler1); + + FileHandler handler2 = new FileHandler(Utils.getGameSubfolder(Constants.FILE_LOG_E).getPath()); + handler2.setFormatter(new LogFormatter(false)); + loggerError.addHandler(handler2); + + loggingEnabled = true; + } catch (Exception e) { + e.printStackTrace(); + } + + logger.setUseParentHandlers(false); + logger.setLevel(Level.ALL); + logger.info("Main logger initialized."); + logger.info((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")).format(new Date())); + + loggerError.setUseParentHandlers(false); + loggerError.setLevel(Level.ALL); + loggerError.info("Error logger initialized."); + loggerError.info((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")).format(new Date())); + + org.newdawn.slick.util.Log.setVerbose(false); + } + + + /** + * Enable logging. + * + * @param flag do enable logging + */ + public static void enable(boolean flag) + { + loggingEnabled = flag; + } + + + /** + * Enable debug mode - log also printed to stdout. + * + * @param printToStdout + */ + public static void setPrintToStdout(boolean printToStdout) + { + Log.printToStdout = printToStdout; + } + + + /** + * Log INFO message + * + * @param msg message + */ + private static void info(String msg) + { + if (!loggingEnabled) { + return; + } + logger.log(Level.INFO, msg); + } + + + /** + * Log FINE (important) message + * + * @param msg message + */ + private static void fine(String msg) + { + if (!loggingEnabled) { + return; + } + logger.log(Level.FINE, msg); + } + + + /** + * Log FINER (loss important) message + * + * @param msg message + */ + private static void finer(String msg) + { + if (!loggingEnabled) { + return; + } + logger.log(Level.FINER, msg); + } + + + /** + * Log FINEST (least important) message + * + * @param msg message + */ + private static void finest(String msg) + { + if (!loggingEnabled) { + return; + } + logger.log(Level.FINEST, msg); + } + + + /** + * Log WARNING message + * + * @param msg message + */ + private static void warning(String msg) + { + if (!loggingEnabled) { + return; + } + logger.log(Level.WARNING, msg); + loggerError.log(Level.WARNING, msg); + } + + + /** + * Log FINE message + * + * @param msg message + */ + public static void f1(String msg) + { + fine(msg); + } + + + /** + * Log FINER message + * + * @param msg message + */ + public static void f2(String msg) + { + finer(msg); + } + + + /** + * Log FINEST message + * + * @param msg message + */ + public static void f3(String msg) + { + finest(msg); + } + + + /** + * Log INFO message + * + * @param msg message + */ + public static void i(String msg) + { + info(msg); + } + + + /** + * Log WARNING message (less severe than ERROR) + * + * @param msg message + */ + public static void w(String msg) + { + warning(msg); + } + + + /** + * Log SEVERE (critical warning) message + * + * @param msg message + */ + private static void severe(String msg) + { + loggerError.log(Level.SEVERE, msg); + if (!loggingEnabled) { + return; + } + logger.log(Level.SEVERE, msg); + } + + + /** + * Log ERROR message + * + * @param msg message + */ + public static void e(String msg) + { + severe(msg); + } + + + /** + * Log THROWING message + * + * @param msg message + * @param thrown thrown exception + */ + public static void e(String msg, Throwable thrown) + { + loggerError.log(Level.SEVERE, msg + "\n" + getStackTrace(thrown)); + if (!loggingEnabled) { + return; + } + logger.log(Level.SEVERE, msg + "\n" + getStackTrace(thrown)); + } + + + /** + * Log exception thrown + * + * @param thrown thrown exception + */ + public static void e(Throwable thrown) + { + loggerError.log(Level.SEVERE, getStackTrace(thrown)); + if (!loggingEnabled) { + return; + } + logger.log(Level.SEVERE, getStackTrace(thrown)); + } + + + /** + * Get stack trace from throwable + * + * @param t + * @return trace + */ + private static String getStackTrace(Throwable t) + { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw, true); + t.printStackTrace(pw); + pw.flush(); + sw.flush(); + return sw.toString(); + } + + /** + * PowerCraft Log file formatter. + * + * @author MightyPork + * @copy (c) 2012 + */ + private static class LogFormatter extends Formatter { + + /** Newline string constant */ + private static final String nl = System.getProperty("line.separator"); + private boolean sysout; + + + public LogFormatter(boolean sysout) { + this.sysout = sysout; + } + + + @Override + public String format(LogRecord record) + { + StringBuffer buf = new StringBuffer(180); + + if (record.getMessage().equals("\n")) { + return nl; + } + + if (record.getMessage().charAt(0) == '\n') { + buf.append(nl); + record.setMessage(record.getMessage().substring(1)); + } + + Level level = record.getLevel(); + String trail = "[ ? ]"; + if (level == Level.FINE) { + trail = "[ # ] "; + } + if (level == Level.FINER) { + trail = "[ - ] "; + } + if (level == Level.FINEST) { + trail = "[ ] "; + } + if (level == Level.INFO) { + trail = "[ i ] "; + } + if (level == Level.SEVERE) { + trail = "[!E!] "; + } + if (level == Level.WARNING) { + trail = "[!W!] "; + } + + record.setMessage(record.getMessage().replaceAll("\n", nl + trail)); + + buf.append(trail); + buf.append(formatMessage(record)); + + buf.append(nl); + + Throwable throwable = record.getThrown(); + if (throwable != null) { + buf.append("at "); + buf.append(record.getSourceClassName()); + buf.append('.'); + buf.append(record.getSourceMethodName()); + buf.append(nl); + + StringWriter sink = new StringWriter(); + throwable.printStackTrace(new PrintWriter(sink, true)); + buf.append(sink.toString()); + + buf.append(nl); + } + + if (Log.printToStdout && sysout) { + if (level == Level.FINE || level == Level.FINER || level == Level.FINEST || level == Level.INFO) { + System.out.print(buf.toString()); + } else if (level == Level.SEVERE || level == Level.WARNING) { + System.err.print(buf.toString()); + } + } + + return buf.toString(); + } + } + +} diff --git a/src/net/tortuga/util/ObjParser.java b/src/net/tortuga/util/ObjParser.java new file mode 100644 index 0000000..6919274 --- /dev/null +++ b/src/net/tortuga/util/ObjParser.java @@ -0,0 +1,348 @@ +package net.tortuga.util; + + +import com.porcupine.coord.Coord; +import com.porcupine.coord.CoordI; +import com.porcupine.math.Range; + + +/** + * Utility for converting Object to data types; Can also convert strings to data + * types. + * + * @author MightyPork + */ +public class ObjParser { + + /** + * Get INTEGER + * + * @param o object + * @param def default value + * @return integer + */ + public static int getInteger(Object o, Integer def) + { + try { + if (o == null) return def; + if (o instanceof String) return (int) Math.round(Double.parseDouble((String) o)); + if (o instanceof Number) return ((Number) o).intValue(); + if (o instanceof Range) return ((Range) o).randInt(); + if (o instanceof Boolean) return ((Boolean) o) ? 1 : 0; + } catch (NumberFormatException e) {} + Log.w("Object cast error - cannot cast " + o + " to Integer."); + return def; + } + + + /** + * Get DOUBLE + * + * @param o object + * @param def default value + * @return double + */ + public static double getDouble(Object o, Double def) + { + return getDoubleImpl(o, def); + } + + + /** + * Get DOUBLE + * + * @param o object + * @param def default value + * @return double + */ + public static double getDouble(Object o, double def) + { + return getDoubleImpl(o, def); + } + + + private static double getDoubleImpl(Object o, Double def) + { + try { + if (o == null) return def; + if (o instanceof String) return Double.parseDouble((String) o); + if (o instanceof Number) return ((Number) o).doubleValue(); + if (o instanceof Range) return ((Range) o).randDouble(); + if (o instanceof Boolean) return ((Boolean) o) ? 1 : 0; + } catch (NumberFormatException e) {} + Log.w("Object cast error - cannot cast " + o + " to Double."); + return def; + } + + + /** + * Get FLOAT + * + * @param o object + * @param def default value + * @return float + */ + public static double getFloat(Object o, Float def) + { + try { + if (o == null) return def; + if (o instanceof Number) return ((Number) o).floatValue(); + } catch (NumberFormatException e) {} + Log.w("Object cast error - cannot cast " + o + " to Float."); + return def; + } + + + /** + * Get BOOLEAN + * + * @param o object + * @param def default value + * @return boolean + */ + public static boolean getBoolean(Object o, Boolean def) + { + if (o == null) return def; + + if (o instanceof String) { + String s = ((String) o).trim().toLowerCase(); + if (s.equals("0")) return false; + if (s.equals("1")) return true; + try { + double n = Double.parseDouble(s); + return n != 0; + } catch (NumberFormatException e) {} + + if (s.equals("true")) return true; + if (s.equals("yes")) return true; + if (s.equals("enabled")) return true; + + if (s.equals("false")) return false; + if (s.equals("no")) return false; + if (s.equals("disabled")) return true; + } + + if (o instanceof Boolean) return ((Boolean) o).booleanValue(); + if (o instanceof Number) return ((Number) o).intValue() != 0; + Log.w("Object cast error - cannot cast " + o + " to Boolean."); + return def; + } + + + /** + * Get STRING + * + * @param o object + * @param def default value + * @return String + */ + public static String getString(Object o, String def) + { + if (o == null) return def; + if (o instanceof String) return ((String) o); +// Log.w("Object cast error - cannot cast " + o + " to String."); + return o.toString(); + } + + + /** + * Get AI_COORD
+ * Converts special constants to magic coordinate instances. + * + * @param o object + * @param def default value + * @return AiCoord + */ + public static Coord getCoord(Object o, Coord def) + { + try { + if (o == null) return def; + if (o instanceof String) { + String s = ((String) o).trim().toUpperCase(); + + // colon to semicolon + s = s.replace(':', ';'); + // comma to semicolon + s = s.replace(',', ';'); + // remove brackets if any + s = s.replaceAll("[\\(\\[\\{\\)\\]\\}]", ""); + String[] parts = s.split("[;]"); + return new Coord(Double.parseDouble(parts[0].trim()), Double.parseDouble(parts[1].trim())); + } + if (o instanceof Coord) return new Coord((Coord) o); + if (o instanceof CoordI) return ((CoordI) o).toCoord(); + } catch (NumberFormatException e) {} + Log.w("Object cast error - cannot cast " + o + " to AiCoord."); + return def; //new AiCoord(0, 0, EAiCoordSpecialType.BASIC); + } + + + /** + * Get RANGE + * + * @param o object + * @param def default value + * @return AiCoord + */ + public static Range getRange(Object o, Range def) + { + try { + if (o == null) return def; + if (o instanceof Number) return new Range(((Number) o).doubleValue(), ((Number) o).doubleValue()); + if (o instanceof String) { + String s = ((String) o).trim(); + + // colon to semicolon + s = s.replace(',', ':'); + // comma to dot. + s = s.replace(';', ':'); + // dash + s = s.replaceAll("([0-9])\\s?[\\-]", "$1:"); + // remove brackets if any + s = s.replaceAll("[\\(\\[\\{\\)\\]\\}]", ""); + String[] parts = s.split("[:]"); + if (parts.length == 2) return new Range(Double.parseDouble(parts[0].trim()), Double.parseDouble(parts[1].trim())); + return new Range(Double.parseDouble(parts[0].trim()), Double.parseDouble(parts[0].trim())); + + } + if (o instanceof Range) return (Range) o; + } catch (NumberFormatException e) {} + Log.w("Object cast error - cannot cast " + o + " to AiRange."); + return def; + } + + + /** + * Get INTEGER + * + * @param o object + * @return integer + */ + public static int getInteger(Object o) + { + return getInteger(o, 0); + } + + + /** + * Get DOUBLE + * + * @param o object + * @return double + */ + public static double getDouble(Object o) + { + return getDouble(o, 0d); + } + + + /** + * Get FLOAT + * + * @param o object + * @return float + */ + public static double getFloat(Object o) + { + return getFloat(o, 0f); + } + + + /** + * Get BOOLEAN + * + * @param o object + * @return boolean + */ + public static boolean getBoolean(Object o) + { + return getBoolean(o, false); + } + + + /** + * Get STRING + * + * @param o object + * @return String + */ + public static String getString(Object o) + { + return getString(o, ""); + } + + + /** + * Get AI_COORD (if special string constant is present instead, build coord + * of it) + * + * @param o object + * @return AiCoord + */ + public static Coord getCoord(Object o) + { + return getCoord(o, Coord.ZERO.copy()); + } + + + /** + * Get RANGE + * + * @param o object + * @return AiCoord + */ + public static Range getRange(Object o) + { + return getRange(o, new Range()); + } + +// /** +// * Get if object is a NUMBER +// * +// * @param o object +// * @return is number +// */ +// public static boolean isNum(Object o) { +// return o instanceof Number; +// } +// +// /** +// * Get if object is a BOOLEAN +// * +// * @param o object +// * @return is boolean +// */ +// public static boolean isBool(Object o) { +// return o instanceof Boolean; +// } +// +// /** +// * Get if object is a STRING +// * +// * @param o object +// * @return is string +// */ +// public static boolean isString(Object o) { +// return o instanceof String; +// } +// +// /** +// * Get if object is a Coord (any) +// * +// * @param o object +// * @return is coord +// */ +// public static boolean isCoord(Object o) { +// return o instanceof Coord || o instanceof AiCoord || o instanceof CoordI; +// } +// +// /** +// * Get if object is a AiRange +// * +// * @param o object +// * @return is range +// */ +// public static boolean isRange(Object o) { +// return o instanceof AiRange; +// } +} diff --git a/src/net/tortuga/util/RenderUtils.java b/src/net/tortuga/util/RenderUtils.java new file mode 100644 index 0000000..361bce2 --- /dev/null +++ b/src/net/tortuga/util/RenderUtils.java @@ -0,0 +1,920 @@ +package net.tortuga.util; + + +import static org.lwjgl.opengl.GL11.*; +import net.tortuga.textures.TextureManager; +import net.tortuga.textures.TxQuad; + +import org.newdawn.slick.opengl.Texture; + +import com.porcupine.color.HSV; +import com.porcupine.color.RGB; +import com.porcupine.coord.Coord; +import com.porcupine.coord.Rect; + + +/** + * Render utilities + * + * @author MightyPork + */ +public class RenderUtils { + + /** + * Render quad 2D + * + * @param min min coord + * @param max max coord + */ + public static void quadCoord(Coord min, Coord max) + { + quadCoord(min.x, min.y, max.x, max.y); + } + + + /** + * Render quad with absolute coords (from screen bottom) + * + * @param left left x + * @param bottom bottom y + * @param right right x + * @param top top y + */ + public static void quadCoord(double left, double bottom, double right, double top) + { + glBegin(GL_QUADS); + glVertex2d(left, top); + glVertex2d(right, top); + glVertex2d(right, bottom); + glVertex2d(left, bottom); + glEnd(); + } + + + /** + * Render quad with coloured border + * + * @param min min + * @param max max + * @param border border width + * @param borderColor border color + * @param insideColor filling color + */ + public static void quadCoordBorder(Coord min, Coord max, double border, RGB borderColor, RGB insideColor) + { + quadCoordBorder(min.x, min.y, max.x, max.y, border, borderColor, insideColor); + } + + + /** + * Render quad with coloured border + * + * @param minX min x + * @param minY min y + * @param maxX max x + * @param maxY max y + * @param border border width + * @param borderColor border color + * @param insideColor filling color + */ + public static void quadCoordBorder(double minX, double minY, double maxX, double maxY, double border, RGB borderColor, RGB insideColor) + { + double bdr = border; + + RenderUtils.setColor(borderColor); + + //@formatter:off + RenderUtils.quadCoord(minX, minY, minX + bdr, maxY); + RenderUtils.quadCoord(maxX - bdr, minY, maxX, maxY); + RenderUtils.quadCoord(minX + bdr, maxY - bdr, maxX - bdr, maxY); + RenderUtils.quadCoord(minX + bdr, minY, maxX - bdr, minY + bdr); + //@formatter:on + + if (insideColor != null) { + RenderUtils.setColor(insideColor); + RenderUtils.quadCoord(minX + bdr, minY + bdr, maxX - bdr, maxY - bdr); + } + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param min min coord + * @param max max coord + * @param colorLeft color left + * @param colorRight color right + */ + public static void quadCoordGradH(Coord min, Coord max, RGB colorLeft, RGB colorRight) + { + quadCoordGradH(min.x, min.y, max.x, max.y, colorLeft, colorRight); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param minX units from left + * @param minY units from bottom + * @param maxX quad width + * @param maxY quad height + * @param colorLeft color left + * @param colorRight color right + */ + public static void quadCoordGradH(double minX, double minY, double maxX, double maxY, RGB colorLeft, RGB colorRight) + { + glBegin(GL_QUADS); + setColor(colorLeft); + glVertex2d(minX, maxY); + setColor(colorRight); + glVertex2d(maxX, maxY); + + setColor(colorRight); + glVertex2d(maxX, minY); + setColor(colorLeft); + glVertex2d(minX, minY); + glEnd(); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param min min coord + * @param max max coord + * @param colorOuter color outer + * @param colorMiddle color inner + */ + public static void quadCoordGradHBilinear(Coord min, Coord max, RGB colorOuter, RGB colorMiddle) + { + quadCoordGradHBilinear(min.x, min.y, max.x, max.y, colorOuter, colorMiddle); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param minX min x + * @param minY min y + * @param maxX max x + * @param maxY max y + * @param colorOuter color outer + * @param colorMiddle color inner + */ + public static void quadCoordGradHBilinear(double minX, double minY, double maxX, double maxY, RGB colorOuter, RGB colorMiddle) + { + glBegin(GL_QUADS); + setColor(colorOuter); + glVertex2d(minX, maxY); + setColor(colorMiddle); + glVertex2d((minX + maxX) / 2, maxY); + + setColor(colorMiddle); + glVertex2d((minX + maxX) / 2, minY); + setColor(colorOuter); + glVertex2d(minX, minY); + + setColor(colorMiddle); + glVertex2d((minX + maxX) / 2, maxY); + setColor(colorOuter); + glVertex2d(maxX, maxY); + + setColor(colorOuter); + glVertex2d(maxX, minY); + setColor(colorMiddle); + glVertex2d((minX + maxX) / 2, minY); + glEnd(); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param min min coord + * @param max max coord + * @param colorTop top color + * @param colorBottom bottom color + */ + public static void quadCoordGradV(Coord min, Coord max, RGB colorTop, RGB colorBottom) + { + quadCoordGradV(min.x, min.y, max.x, max.y, colorTop, colorBottom); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param minX min X + * @param minY min Y + * @param maxX max X + * @param maxY max Y + * @param colorTop top color + * @param colorBottom bottom color + */ + public static void quadCoordGradV(double minX, double minY, double maxX, double maxY, RGB colorTop, RGB colorBottom) + { + glBegin(GL_QUADS); + + setColor(colorTop); + glVertex2d(minX, maxY); + glVertex2d(maxX, maxY); + + setColor(colorBottom); + glVertex2d(maxX, minY); + glVertex2d(minX, minY); + glEnd(); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param min min coord + * @param max max coord + * @param colorOuter outer color + * @param colorMiddle middle color + */ + public static void quadCoordGradVBilinear(Coord min, Coord max, RGB colorOuter, RGB colorMiddle) + { + quadCoordGradVBilinear(min.x, min.y, max.x, max.y, colorOuter, colorMiddle); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param minX min X + * @param minY min Y + * @param maxX max X + * @param maxY max Y + * @param colorOuter outer color + * @param colorMiddle middle color + */ + public static void quadCoordGradVBilinear(double minX, double minY, double maxX, double maxY, RGB colorOuter, RGB colorMiddle) + { + glBegin(GL_QUADS); + + setColor(colorOuter); + glVertex2d(minX, maxY); + glVertex2d(maxX, maxY); + + setColor(colorMiddle); + glVertex2d(maxX, (maxY + minY) / 2); + glVertex2d(minX, (maxY + minY) / 2); + + setColor(colorMiddle); + glVertex2d(minX, (maxY + minY) / 2); + glVertex2d(maxX, (maxY + minY) / 2); + + setColor(colorOuter); + glVertex2d(maxX, minY); + glVertex2d(minX, minY); + + glEnd(); + } + + + /** + * Render quad with coloured border with outset effect + * + * @param min min coord + * @param max max coord + * @param border border width + * @param fill fill color + * @param inset true for inset, false for outset + */ + public static void quadCoordOutset(Coord min, Coord max, double border, RGB fill, boolean inset) + { + quadCoordOutset(min.x, min.y, max.x, max.y, border, fill, inset); + } + + + /** + * Render quad with coloured border with outset effect + * + * @param minX left X + * @param minY bottom Y + * @param maxX right X + * @param maxY top Y + * @param border border width + * @param fill fill color + * @param inset true for inset, false for outset + */ + public static void quadCoordOutset(double minX, double minY, double maxX, double maxY, double border, RGB fill, boolean inset) + { + HSV hsv = fill.toHSV(); + HSV h; + + h = hsv.copy(); + h.s *= 0.6; + RGB a = h.toRGB(); + + h = fill.toHSV(); + h.v *= 0.6; + RGB b = h.toRGB(); + + RGB leftTopC, rightBottomC; + + if (!inset) { + leftTopC = a; + rightBottomC = b; + } else { + leftTopC = b; + rightBottomC = a; + } + + double bdr = border; + + RenderUtils.setColor(leftTopC); + + // left + top + glBegin(GL_QUADS); + glVertex2d(minX, minY); + glVertex2d(minX + bdr, minY + bdr); + glVertex2d(minX + bdr, maxY - bdr); + glVertex2d(minX, maxY); + + glVertex2d(minX, maxY); + glVertex2d(minX + bdr, maxY - bdr); + glVertex2d(maxX - bdr, maxY - bdr); + glVertex2d(maxX, maxY); + glEnd(); + + RenderUtils.setColor(rightBottomC); + + // right + bottom + glBegin(GL_QUADS); + glVertex2d(maxX, minY); + glVertex2d(maxX, maxY); + glVertex2d(maxX - bdr, maxY - bdr); + glVertex2d(maxX - bdr, minY + bdr); + + glVertex2d(minX, minY); + glVertex2d(maxX, minY); + glVertex2d(maxX - bdr, minY + bdr); + glVertex2d(minX + bdr, minY + bdr); + glEnd(); + + RenderUtils.setColor(fill); + RenderUtils.quadCoord(minX + bdr, minY + bdr, maxX - bdr, maxY - bdr); + } + + + /** + * Render quad 2D + * + * @param rect rectangle + */ + public static void quadRect(Rect rect) + { + quadCoord(rect.getMin(), rect.getMax()); + } + + + /** + * Render quad 2D + * + * @param rect rectangle + * @param color draw color + */ + public static void quadRect(Rect rect, RGB color) + { + setColor(color); + quadCoord(rect.getMin(), rect.getMax()); + } + + + /** + * Render quad with coloured border + * + * @param rect rect + * @param border border width + * @param borderColor border color + * @param insideColor filling color + */ + public static void quadBorder(Rect rect, double border, RGB borderColor, RGB insideColor) + { + quadCoordBorder(rect.getMin(), rect.getMax(), border, borderColor, insideColor); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param rect rect + * @param colorLeft color left + * @param colorRight color right + */ + public static void quadGradH(Rect rect, RGB colorLeft, RGB colorRight) + { + quadCoordGradH(rect.getMin(), rect.getMax(), colorLeft, colorRight); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param rect rect + * @param colorOuter color outer + * @param colorMiddle color inner + */ + public static void quadGradHBilinear(Rect rect, RGB colorOuter, RGB colorMiddle) + { + quadCoordGradHBilinear(rect.getMin(), rect.getMax(), colorOuter, colorMiddle); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param rect rect + * @param colorTop top color + * @param colorBottom bottom color + */ + public static void quadGradV(Rect rect, RGB colorTop, RGB colorBottom) + { + quadCoordGradV(rect.getMin(), rect.getMax(), colorTop, colorBottom); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param rect rect + * @param colorOuter outer color + * @param colorMiddle middle color + */ + public static void quadGradVBilinear(Rect rect, RGB colorOuter, RGB colorMiddle) + { + quadCoordGradVBilinear(rect.getMin(), rect.getMax(), colorOuter, colorMiddle); + } + + + /** + * Render quad with coloured border with outset effect + * + * @param rect rectangle + * @param border border width + * @param fill fill color + * @param inset true for inset, false for outset + */ + public static void quadRectOutset(Rect rect, double border, RGB fill, boolean inset) + { + quadCoordOutset(rect.getMin(), rect.getMax(), border, fill, inset); + } + + + /** + * Render textured rect (texture must be binded already) + * + * @param quad rectangle (px) + * @param textureCoords texture coords (0-1) + */ + public static void quadTexturedAbs(Rect quad, Rect textureCoords) + { + double left = quad.x1(); + double bottom = quad.y2(); + double right = quad.x2(); + double top = quad.y1(); + + double tleft = textureCoords.x1(); + double tbottom = textureCoords.y1(); + double tright = textureCoords.x2(); + double ttop = textureCoords.y2(); + + glBegin(GL_QUADS); + glTexCoord2d(tleft, ttop); + glVertex2d(left, top); + glTexCoord2d(tright, ttop); + glVertex2d(right, top); + glTexCoord2d(tright, tbottom); + glVertex2d(right, bottom); + glTexCoord2d(tleft, tbottom); + glVertex2d(left, bottom); + glEnd(); + } + + + /** + * Render textured rect + * + * @param quad rectangle (px) + * @param txCoords texture coords rectangle (px) + * @param texture texture instance + */ + public static void quadTextured(Rect quad, Rect txCoords, Texture texture) + { + quadTextured(quad, txCoords, texture, RGB.WHITE); + } + + + /** + * Render textured rect + * + * @param quad rectangle (px) + * @param txCoords texture coords rectangle (px) + * @param texture texture instance + * @param tint color tint + */ + public static void quadTextured(Rect quad, Rect txCoords, Texture texture, RGB tint) + { + glEnable(GL_TEXTURE_2D); + setColor(tint); + TextureManager.bind(texture); + + quadTexturedAbs(quad, txCoords.div(texture.getImageHeight())); + + TextureManager.unbind(); + glDisable(GL_TEXTURE_2D); + } + + + /** + * Render textured rect + * + * @param quad rectangle (px) + * @param txquad texture quad + */ + public static void quadTextured(Rect quad, TxQuad txquad) + { + quadTextured(quad, txquad, RGB.WHITE); + } + + + /** + * Render textured rect + * + * @param quad rectangle (px) + * @param txquad texture instance + * @param tint color tint + */ + public static void quadTextured(Rect quad, TxQuad txquad, RGB tint) + { + quadTextured(quad, txquad.uvs, txquad.tx, tint); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param yOffsetTimes offset count (move frame down n times) + * @param texture the texture + */ + public static void quadTexturedFrame(Rect quadRect, Rect textureRect, int yOffsetTimes, Texture texture) + { + quadTexturedFrame(quadRect, textureRect, yOffsetTimes, texture, RGB.WHITE); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param yOffsetTimes offset count (move frame down n times) + * @param texture the texture + * @param tint color tint + */ + public static void quadTexturedFrame(Rect quadRect, Rect textureRect, int yOffsetTimes, Texture texture, RGB tint) + { + Texture tx = texture; + + int frameHeight = (int) textureRect.getSize().y; + + int yOffset = yOffsetTimes * frameHeight; + + double x1 = quadRect.x1(); + double y1 = quadRect.y1(); + double x2 = quadRect.x2(); + double y2 = quadRect.y2(); + + double w = x2 - x1; + double h = y2 - y1; + + double tx1 = textureRect.x1(); + double ty1 = textureRect.y1(); + double tx2 = textureRect.x2(); + double ty2 = textureRect.y2(); + + double halfY = h / 2D; + double halfX = w / 2D; + + // lt, rt + // lb, rb + + Rect verts, uvs; + + // lt + verts = new Rect(x1, y2, x1 + halfX, y2 - halfY); + uvs = new Rect(tx1, ty1, tx1 + halfX, ty1 + halfY); + uvs.add_ip(0, yOffset); + quadTextured(verts, uvs, tx, tint); + + // lb + verts = new Rect(x1, y2 - halfY, x1 + halfX, y1); + uvs = new Rect(tx1, ty2 - halfY, tx1 + halfX, ty2); + uvs.add_ip(0, yOffset); + quadTextured(verts, uvs, tx, tint); + + // rt + verts = new Rect(x1 + halfX, y2, x2, y2 - halfY); + uvs = new Rect(tx2 - halfX, ty1, tx2, ty1 + halfY); + uvs.add_ip(0, yOffset); + quadTextured(verts, uvs, tx, tint); + + // rb + verts = new Rect(x1 + halfX, y2 - halfY, x2, y1); + uvs = new Rect(tx2 - halfX, ty2 - halfY, tx2, ty2); + uvs.add_ip(0, yOffset); + quadTextured(verts, uvs, tx, tint); + + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param texture the texture + */ + public static void quadTexturedFrame(Rect quadRect, Rect textureRect, Texture texture) + { + quadTexturedFrame(quadRect, textureRect, 0, texture, RGB.WHITE); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + */ + public static void quadTexturedFrame(Rect quadRect, TxQuad txquad) + { + quadTexturedFrame(quadRect, txquad, 0, RGB.WHITE); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param yOffsetTimes offset count (move frame down n times) + */ + public static void quadTexturedFrame(Rect quadRect, TxQuad txquad, int yOffsetTimes) + { + quadTexturedFrame(quadRect, txquad, yOffsetTimes, RGB.WHITE); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param yOffsetTimes offset count (move frame down n times) + * @param tint color tint + */ + public static void quadTexturedFrame(Rect quadRect, TxQuad txquad, int yOffsetTimes, RGB tint) + { + quadTexturedFrame(quadRect, txquad.uvs, yOffsetTimes, txquad.tx, tint); + } + + + /** + * Render textured frame stretching horizontally (rect height = texture rect + * height) + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param borderSize size of the unstretched horizontal border + * @param texture the texture + */ + public static void quadTexturedStretchH(Rect quadRect, Rect textureRect, int borderSize, Texture texture) + { + quadTexturedStretchH(quadRect, textureRect, borderSize, texture, RGB.WHITE); + } + + + /** + * Render textured frame stretching horizontally (rect height = texture rect + * height) + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param borderSize size of the unstretched horizontal border + * @param texture the texture + * @param tint color tint + */ + public static void quadTexturedStretchH(Rect quadRect, Rect textureRect, int borderSize, Texture texture, RGB tint) + { + Texture tx = texture; + + double x1 = quadRect.x1(); + double y1 = quadRect.y1(); + double x2 = quadRect.x2(); + double y2 = quadRect.y2(); + + double tx1 = textureRect.x1(); + double ty1 = textureRect.y1(); + double tx2 = textureRect.x2(); + double ty2 = textureRect.y2(); + + // lt, mi, rt + + Rect verts, uvs; + + // lt + verts = new Rect(x1, y2, x1 + borderSize, y1); + uvs = new Rect(tx1, ty1, tx1 + borderSize, ty2); + quadTextured(verts, uvs, tx, tint); + +// // mi + verts = new Rect(x1 + borderSize, y2, x2 - borderSize, y1); + uvs = new Rect(tx1 + borderSize, ty1, tx2 - borderSize, ty2); + quadTextured(verts, uvs, tx, tint); + + // rt + verts = new Rect(x2 - borderSize, y2, x2, y1); + uvs = new Rect(tx2 - borderSize, ty1, tx2, ty2); + quadTextured(verts, uvs, tx, tint); + } + + + /** + * Render textured frame stretching horizontally (rect height = texture rect + * height) + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param borderSize size of the unstretched horizontal border + */ + public static void quadTexturedStretchH(Rect quadRect, TxQuad txquad, int borderSize) + { + quadTexturedStretchH(quadRect, txquad, borderSize, RGB.WHITE); + } + + + /** + * Render textured frame stretching horizontally (rect height = texture rect + * height) + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param borderSize size of the unstretched horizontal border + * @param tint color tint + */ + public static void quadTexturedStretchH(Rect quadRect, TxQuad txquad, int borderSize, RGB tint) + { + quadTexturedStretchH(quadRect, txquad.uvs, borderSize, txquad.tx, tint); + } + + + /** + * Render textured frame stretching vertically (rect width = texture rect + * width) + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param borderSize size of the unstretched horizontal border + * @param texture the texture + */ + public static void quadTexturedStretchV(Rect quadRect, Rect textureRect, int borderSize, Texture texture) + { + quadTexturedStretchV(quadRect, textureRect, borderSize, texture, RGB.WHITE); + } + + + /** + * Render textured frame stretching vertically (rect width = texture rect + * width) + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param borderSize size of the unstretched horizontal border + * @param texture the texture + * @param tint color tint + */ + public static void quadTexturedStretchV(Rect quadRect, Rect textureRect, int borderSize, Texture texture, RGB tint) + { + Texture tx = texture; + + double x1 = quadRect.x1(); + double y1 = quadRect.y1(); + double x2 = quadRect.x2(); + double y2 = quadRect.y2(); + + double tx1 = textureRect.x1(); + double ty1 = textureRect.y1(); + double tx2 = textureRect.x2(); + double ty2 = textureRect.y2(); + + // tp + // mi + // bn + + Rect verts, uvs; + + // tp + verts = new Rect(x1, y2, x2, y2 - borderSize); + uvs = new Rect(tx1, ty1, tx2, ty1 + borderSize); + quadTextured(verts, uvs, tx, tint); + + // mi + verts = new Rect(x1, y2 - borderSize, x2, y1 + borderSize); + uvs = new Rect(tx1, ty1 + borderSize, tx2, ty2 - borderSize); + quadTextured(verts, uvs, tx, tint); + + // rt + verts = new Rect(x1, y1 + borderSize, x2, y1); + uvs = new Rect(tx1, ty2 - borderSize, tx2, ty2); + quadTextured(verts, uvs, tx, tint); + } + + + /** + * Render textured frame stretching vertically (rect width = texture rect + * width) + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param borderSize size of the unstretched horizontal border + */ + public static void quadTexturedStretchV(Rect quadRect, TxQuad txquad, int borderSize) + { + quadTexturedStretchV(quadRect, txquad, borderSize, RGB.WHITE); + } + + + /** + * Render textured frame stretching vertically (rect width = texture rect + * width) + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param borderSize size of the unstretched horizontal border + * @param tint color tint + */ + public static void quadTexturedStretchV(Rect quadRect, TxQuad txquad, int borderSize, RGB tint) + { + quadTexturedStretchV(quadRect, txquad.uvs, borderSize, txquad.tx, tint); + + } + + + /** + * Render quad 2D + * + * @param left units from left + * @param bottom units from bottom + * @param width quad width + * @param height quad height + */ + public static void quadSize(double left, double bottom, double width, double height) + { + glBegin(GL_QUADS); + glVertex2d(left, bottom + height); + glVertex2d(left + width, bottom + height); + glVertex2d(left + width, bottom); + glVertex2d(left, bottom); + glEnd(); + } + + + /** + * Bind GL color + * + * @param color RGB color + */ + public static void setColor(RGB color) + { + if (color != null) glColor4d(color.r, color.g, color.b, color.a); + } + + + /** + * Bind GL color + * + * @param color RGB color + * @param alpha alpha multiplier + */ + public static void setColor(RGB color, double alpha) + { + if (color != null) glColor4d(color.r, color.g, color.b, color.a * alpha); + } + + + /** + * Translate with coord + * + * @param coord coord + */ + public static void translate(Coord coord) + { + glTranslated(coord.x, coord.y, coord.z); + } +} diff --git a/src/net/tortuga/util/Utils.java b/src/net/tortuga/util/Utils.java new file mode 100644 index 0000000..b458e2d --- /dev/null +++ b/src/net/tortuga/util/Utils.java @@ -0,0 +1,78 @@ +package net.tortuga.util; + + +import java.io.File; + +import net.tortuga.Constants; + +import com.porcupine.coord.Coord; +import com.porcupine.coord.Vec; +import com.porcupine.math.Calc.Rad; +import com.porcupine.math.Polar; +import com.porcupine.util.FileUtils; + + +/** + * Sector's utils class + * + * @author MightyPork + */ +public class Utils { + + private static File gameDir; + + + /** + * 2D angle of observer to point + * + * @param observer point of observer + * @param point point of target + * @param lookVec look vector of observer + * @return angle + */ + public static double observerAngleToCoord(Coord observer, Coord point, Vec lookVec) + { + Vec dist = observer.vecTo(point); + + Polar point_p = Polar.fromCoord(dist.x, dist.z); + Polar look_p = Polar.fromCoord(lookVec.x, lookVec.z); + + return Rad.toDeg(Rad.diff(point_p.angle, look_p.angle)); + } + + + /** + * Get working directory ending with slash. + * + * @return directory path file + */ + public static File getGameFolder() + { + if (gameDir == null) { + gameDir = FileUtils.getAppDir(Constants.APP_DIR); + } + + return gameDir; + } + + + /** + * Get subfolder of game dir + * + * @param subfolderName sibfolder name + * @return the file object + */ + public static File getGameSubfolder(String subfolderName) + { + return new File(getGameFolder(), subfolderName); + } + + + public static Object fallback(Object... options) + { + for (Object o : options) { + if (o != null) return o; + } + return null; // error + } +} diff --git a/src/net/tortuga/util/loading/DirectoryLoader.java b/src/net/tortuga/util/loading/DirectoryLoader.java new file mode 100644 index 0000000..ea3f6c0 --- /dev/null +++ b/src/net/tortuga/util/loading/DirectoryLoader.java @@ -0,0 +1,30 @@ +package net.tortuga.util.loading; + + +import java.io.InputStream; + + +/** + * Directory access interface + * + * @author MightyPork + */ +public interface DirectoryLoader { + + /** + * Get if file exists in this directory + * + * @param filename file name + * @return exists + */ + public boolean fileExists(String filename); + + + /** + * Open file as InputStream + * + * @param filename file name + * @return input stream + */ + public InputStream openFile(String filename); +} diff --git a/src/net/tortuga/util/loading/ResourceDirectoryLoader.java b/src/net/tortuga/util/loading/ResourceDirectoryLoader.java new file mode 100644 index 0000000..48cc1f5 --- /dev/null +++ b/src/net/tortuga/util/loading/ResourceDirectoryLoader.java @@ -0,0 +1,50 @@ +package net.tortuga.util.loading; + + +import java.io.InputStream; + +import org.newdawn.slick.util.ResourceLoader; + + +/** + * Directory access in resources + * + * @author MightyPork + */ +public class ResourceDirectoryLoader implements DirectoryLoader { + + private String path; + + + /** + * Create resource directory access + * + * @param path the path + */ + public ResourceDirectoryLoader(String path) { + this.path = path; + if (path.endsWith("/")) this.path = path.substring(0, path.length() - 1); + } + + + @Override + public boolean fileExists(String filename) + { + return ResourceLoader.resourceExists(path + "/" + filename); + } + + + @Override + public InputStream openFile(String filename) + { + return ResourceLoader.getResourceAsStream(path + "/" + filename); + } + + + @Override + public String toString() + { + return "Res('" + path + "')"; + } + +} diff --git a/src/net/tortuga/util/loading/XmlUtil.java b/src/net/tortuga/util/loading/XmlUtil.java new file mode 100644 index 0000000..9174ca2 --- /dev/null +++ b/src/net/tortuga/util/loading/XmlUtil.java @@ -0,0 +1,196 @@ +package net.tortuga.util.loading; + + +import java.io.*; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import net.tortuga.Constants; +import net.tortuga.util.Log; +import net.tortuga.util.ObjParser; + +import org.jdom2.Attribute; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.input.SAXBuilder; + + +/** + * XML loading utility + * + * @author MightyPork + */ +public class XmlUtil { + + private static final boolean DEBUG = Constants.LOG_XML_LOADING; + + + /** + * Get XML document from input stream + * + * @param in input stream + * @return document + */ + public static Document getDocument(InputStream in) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // Fake code simulating the copy + // You can generally do better with nio if you need... + // And please, unlike me, do something about the Exceptions :D + byte[] buffer = new byte[1024]; + int len; + try { + while ((len = in.read(buffer)) > -1) { + baos.write(buffer, 0, len); + } + baos.flush(); + + } catch (IOException e) { + e.printStackTrace(); + } + + // Open new InputStreams using the recorded bytes + // Can be repeated as many times as you wish + InputStream is1 = new ByteArrayInputStream(baos.toByteArray()); + InputStream is2 = new ByteArrayInputStream(baos.toByteArray()); + + try { + SAXBuilder builder = new SAXBuilder(); + Document d = builder.build(is1); + return d; + } catch (IOException io) { + Log.e("XmlUtil: " + io.getMessage()); + } catch (JDOMException jdomex) { + Log.e("XmlUtil: " + jdomex.getMessage()); + } + + try { + String msg = ""; + + msg += "\n\nInvalid XML file:\n"; + InputStreamReader sr = new InputStreamReader(is2); + BufferedReader br = new BufferedReader(sr); + String line; + while ((line = br.readLine()) != null) { + msg += "\t" + line + "\n"; + } + msg += "End of file.\n\n"; + + Log.e(msg); + } catch (IOException e) { + Log.e("IO Error:", e); + } + + return null; + } + + + /** + * Get root element of an input stream XML + * + * @param in the input stream + * @return root element + */ + public static Element getRootElement(InputStream in) + { + return getDocument(in).getRootElement(); + } + + + /** + * Load all files from directory, described in manifest. There can be + * multiple file lists in manifest, each with different "fileListTagName". + * + * @param dirLoader directory loader + * @param manifestFileListTag tag in manifest, containing elements + * <file>filename.xml</file> which describe loading + * order of the directory files. + * @return map of Filename → Root Element + */ + public static Map loadFromDirectoryWithManifest(DirectoryLoader dirLoader, String manifestFileListTag) + { + Map roots = new LinkedHashMap(); + + if (DEBUG) Log.f2("XmlUtil: Request to load documents from directory " + dirLoader + "."); + + if (!dirLoader.fileExists("manifest.xml")) { + Log.w("XmlUtil: Folder accessed by " + dirLoader + " has no 'manifest.xml'."); + return null; + } + + InputStream is = dirLoader.openFile("manifest.xml"); + if (DEBUG) Log.f3("Loading manifest file."); + Document doc = getDocument(is); + Element root = doc.getRootElement(); + Element fileList = root.getChild(manifestFileListTag); + List files = fileList.getChildren("file"); + + for (Element elem : files) { + String fname = elem.getText().trim(); + + if (DEBUG) Log.f3("Loading file " + fname); + InputStream is2 = dirLoader.openFile(fname); + Document doc2 = getDocument(is2); + if (doc2 != null) roots.put(fname, doc2.getRootElement()); + } + + return roots; + } + + + /** + * Load arguments from wrapper tag + * + * @param wrapper wrapper tag + * @return map of the arguments + */ + public static Map loadArgs(Element wrapper) + { + Map args = new HashMap(); + + for (Element argNode : wrapper.getChildren()) { + String argName = argNode.getName().trim().toLowerCase(); + + List at2 = argNode.getAttributes(); + + if (at2.size() == 0) { + Log.w("Parsing XML file: Argument '" + argName + "' has no value in '" + wrapper.getName() + ":" + wrapper.getAttributes() + "'."); + continue; + } + + String argType = at2.get(0).getName().trim().toLowerCase(); + String argValueSrc = at2.get(0).getValue().trim(); + + Object argValue = null; + + if (argType.equals("str") || argType.equals("string") || argType.equals("s") || argType.equals("name")) { + argValue = ObjParser.getString(argValueSrc); + + } else if (argType.equals("num") || argType.equals("float") || argType.equals("double")) { + argValue = ObjParser.getDouble(argValueSrc); + + } else if (argType.equals("int")) { + argValue = ObjParser.getInteger(argValueSrc); + + } else if (argType.equals("bool") || argType.equals("boolean") || argType.equals("flag") || argType.equals("on") || argType.equals("state")) { + argValue = ObjParser.getBoolean(argValueSrc); + + } else if (argType.equals("coord") || argType.equals("vec") || argType.equals("dir") || argType.equals("coordinate") || argType.equals("vector") || argType.equals("direction")) { + argValue = ObjParser.getCoord(argValueSrc); + + } else if (argType.equals("range") || argType.equals("rand") || argType.equals("scale")) { + argValue = ObjParser.getRange(argValueSrc); + } + + // argName, argValue + + // add new arg to args map + args.put(argName, argValue); + } + + return args; + } +}