├── scage-logo.png ├── phys2d-060408.jar ├── rotating_hello.png ├── src ├── main │ ├── resources │ │ ├── maven.properties │ │ ├── resources │ │ │ ├── fonts │ │ │ │ └── DroidSans.ttf │ │ │ └── images │ │ │ │ └── scage-logo.png │ │ ├── controller-actor-system.conf │ │ └── logback.xml │ └── scala │ │ └── com │ │ └── github │ │ └── dunnololda │ │ └── scage │ │ ├── support │ │ ├── ScageId.scala │ │ ├── physics │ │ │ ├── objects │ │ │ │ ├── StaticBall.scala │ │ │ │ ├── DynaBall.scala │ │ │ │ ├── StaticLine.scala │ │ │ │ ├── StaticBox.scala │ │ │ │ ├── DynaBox.scala │ │ │ │ └── StaticPolygon.scala │ │ │ ├── Physical.scala │ │ │ └── ScagePhysics.scala │ │ ├── messages │ │ │ ├── unicode │ │ │ │ ├── Effect.java │ │ │ │ ├── ConfigurableEffect.java │ │ │ │ ├── ColorEffect.java │ │ │ │ ├── MyImage.java │ │ │ │ ├── Glyph.java │ │ │ │ └── GlyphPage.java │ │ │ ├── ColoredString.scala │ │ │ ├── ScageMessageD.scala │ │ │ ├── ScageXML.scala │ │ │ └── ScageMessage.scala │ │ ├── XInitThreadsCaller.scala │ │ ├── PathFinder.scala │ │ ├── tracer3 │ │ │ ├── Trace.scala │ │ │ ├── CoordTracer.scala │ │ │ └── ScageTracer.scala │ │ ├── ScageColorTest.scala │ │ ├── parsers │ │ │ └── VecParser.scala │ │ ├── Events.scala │ │ ├── SortedBuffer.scala │ │ ├── LWJGLKeyboard.scala │ │ ├── Vec.scala │ │ └── ScageColor.scala │ │ ├── handlers │ │ ├── controller2 │ │ │ ├── ScageController.scala │ │ │ └── SingleController.scala │ │ └── controller3 │ │ │ └── ActorSingleController.scala │ │ ├── ScageScreen.scala │ │ └── ScageLib.scala └── test │ ├── resources │ ├── colortest-properties.txt │ ├── resources │ │ ├── fonts │ │ │ ├── comic.ttf │ │ │ └── DroidSans.ttf │ │ ├── strings │ │ │ ├── scagetest_strings_ru.xml │ │ │ └── scagetest_strings_en.xml │ │ └── interfaces │ │ │ ├── scagetest_interfaces_en.xml │ │ │ └── scagetest_interfaces_ru.xml │ └── scagetest-properties.txt │ └── scala │ └── com │ └── github │ └── dunnololda │ └── scage │ └── test │ └── ScageTest.scala ├── .gitignore ├── xdll.cpp ├── project └── Build.scala └── README.md /scage-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delorum/scage/HEAD/scage-logo.png -------------------------------------------------------------------------------- /phys2d-060408.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delorum/scage/HEAD/phys2d-060408.jar -------------------------------------------------------------------------------- /rotating_hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delorum/scage/HEAD/rotating_hello.png -------------------------------------------------------------------------------- /src/main/resources/maven.properties: -------------------------------------------------------------------------------- 1 | app.version = ${project.version} 2 | app.name = ${project.artifactId} -------------------------------------------------------------------------------- /src/test/resources/colortest-properties.txt: -------------------------------------------------------------------------------- 1 | app.name = Color Test 2 | app.version = 1.1 3 | 4 | screen.width = 640 5 | screen.height = 480 -------------------------------------------------------------------------------- /src/test/resources/resources/fonts/comic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delorum/scage/HEAD/src/test/resources/resources/fonts/comic.ttf -------------------------------------------------------------------------------- /src/main/resources/resources/fonts/DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delorum/scage/HEAD/src/main/resources/resources/fonts/DroidSans.ttf -------------------------------------------------------------------------------- /src/test/resources/resources/fonts/DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delorum/scage/HEAD/src/test/resources/resources/fonts/DroidSans.ttf -------------------------------------------------------------------------------- /src/main/resources/resources/images/scage-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delorum/scage/HEAD/src/main/resources/resources/images/scage-logo.png -------------------------------------------------------------------------------- /src/main/resources/controller-actor-system.conf: -------------------------------------------------------------------------------- 1 | controller-system { 2 | my-pinned-dispatcher { 3 | executor = "thread-pool-executor" 4 | type = PinnedDispatcher 5 | } 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | .classpath 3 | .project 4 | .idea 5 | .svn 6 | *.iml 7 | *.ipr 8 | *.iws 9 | target 10 | local-build.properties 11 | libs 12 | settings.xml 13 | libxx.so 14 | -------------------------------------------------------------------------------- /src/test/resources/resources/strings/scagetest_strings_ru.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Строка с кодом ? не найдена 5 | 6 | 7 | 8 | Привет, Мир! 9 | 10 | 11 | 12 | WASD и Левая Кнопка Мыши, 13 | Колесико - приблизить/отдалить 14 | 15 | -------------------------------------------------------------------------------- /src/test/resources/resources/strings/scagetest_strings_en.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The string with code ? not found 5 | 6 | 7 | 8 | Hello World! 9 | 10 | 11 | 12 | Use WASD to move, Left Mouse Button to shoot and 13 | Mouse Wheel for zoom 14 | 15 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/ScageId.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support 2 | 3 | trait ScageIdTrait { 4 | def nextId:Int 5 | } 6 | 7 | object ScageId extends ScageIdTrait { // TODO: add some id rotation algorithm or throw error and exit on id amount exceeded 8 | protected var id = 10000 // maybe switch to Long, BigInt or something 9 | def nextId = { 10 | synchronized { 11 | id += 1 12 | id 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /xdll.cpp: -------------------------------------------------------------------------------- 1 | //-------------------------------------------------------------------------------------------------- 2 | //xdll.cpp 3 | //-------------------------------------------------------------------------------------------------- 4 | 5 | // compile with g++ -o libxx.so -shared -fPIC -Wl,-soname,libxx.so -L/usr/lib/X11 -I/usr/include/X11 xdll.cpp -lX11 6 | 7 | #include 8 | #include 9 | 10 | class a{ 11 | public: 12 | a() { XInitThreads(); } 13 | }; 14 | 15 | a X; 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %date [%-5level] %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/test/resources/scagetest-properties.txt: -------------------------------------------------------------------------------- 1 | xml.lang = ru 2 | 3 | screen.width=640 4 | screen.height=480 5 | 6 | field.from.x = 20 7 | field.to.x = screen.width - 60 8 | field.N_x = (field.to.x - field.from.x)/40 9 | 10 | field.from.y = 20 11 | field.to.y = screen.height - 20 12 | field.N_y = (field.to.y - field.from.y)/40 13 | 14 | field.solid_edges = off 15 | 16 | screen.splash = resources/images/scage-logo.png 17 | 18 | physics.restitution = on 19 | physics.dt = 1 20 | physics.gravity = [-1, -1] 21 | 22 | rect.color = green 23 | 24 | #render.framerate = 15 25 | 26 | xml.interfaces.base = resources/interfaces/scagetest_interfaces 27 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/physics/objects/StaticBall.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.physics.objects 2 | 3 | import net.phys2d.raw.shapes.Circle 4 | import net.phys2d.raw.StaticBody 5 | import com.github.dunnololda.scage.support.Vec 6 | import com.github.dunnololda.scage.support.physics.Physical 7 | import com.github.dunnololda.cli.AppProperties._ 8 | 9 | class StaticBall(init_coord:Vec, val radius:Int, restitution:Boolean = property("physics.restitution", true)) extends Physical { 10 | val body = new StaticBody("StaticBall", new Circle(radius)) 11 | if(restitution) body.setRestitution(1.0f) 12 | body.setPosition(init_coord.x, init_coord.y) 13 | 14 | def points = Array(coord) 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/physics/objects/DynaBall.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.physics.objects 2 | 3 | import net.phys2d.raw.Body 4 | import com.github.dunnololda.scage.support.Vec 5 | import net.phys2d.raw.shapes.Circle 6 | import com.github.dunnololda.scage.support.physics.Physical 7 | import com.github.dunnololda.cli.AppProperties._ 8 | import java.lang.Float 9 | 10 | class DynaBall(init_coord:Vec, val radius:Int, mass:Float = 1, restitution:Boolean = property("physics.restitution", true)) extends Physical { 11 | val body = new Body(new Circle(radius), mass) 12 | if(restitution) body.setRestitution(1.0f) 13 | body.setPosition(init_coord.x, init_coord.y) 14 | 15 | def points = Array(coord) 16 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/unicode/Effect.java: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages.unicode; 2 | 3 | import java.awt.*; 4 | import java.awt.image.BufferedImage; 5 | 6 | /** 7 | * A graphical effect that is applied to glyphs in a {@link UnicodeFont}. 8 | * 9 | * @author Nathan Sweet 10 | */ 11 | public interface Effect { 12 | /** 13 | * Called to draw the effect. 14 | * 15 | * @param image The image to draw into 16 | * @param g The graphics context to use for applying the effect 17 | * @param unicodeFont The font being rendered 18 | * @param glyph The particular glyph being rendered 19 | */ 20 | public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/physics/objects/StaticLine.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.physics.objects 2 | 3 | import net.phys2d.raw.shapes.Line 4 | import net.phys2d.raw.StaticBody 5 | import com.github.dunnololda.scage.support.Vec 6 | import com.github.dunnololda.scage.support.physics.Physical 7 | import com.github.dunnololda.cli.AppProperties._ 8 | 9 | class StaticLine(start:Vec, end:Vec, restitution:Boolean = property("physics.restitution", true)) extends Physical { 10 | private val line = new Line((end-start).x, (end-start).y) 11 | 12 | val body = new StaticBody("line", line) 13 | if(restitution) body.setRestitution(1.0f) 14 | body.setPosition(start.x, start.y) 15 | 16 | def points = line.getVertices(body.getPosition, body.getRotation).map(v => Vec(v.getX, v.getY)) 17 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/physics/objects/StaticBox.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.physics.objects 2 | 3 | import net.phys2d.raw.StaticBody 4 | import net.phys2d.raw.shapes.Box 5 | import com.github.dunnololda.scage.support.Vec 6 | import com.github.dunnololda.scage.support.physics.Physical 7 | import com.github.dunnololda.cli.AppProperties._ 8 | 9 | class StaticBox(init_coord:Vec, val box_width:Float, val box_height:Float, restitution:Boolean = property("physics.restitution", true)) extends Physical { 10 | val box = new Box(box_width, box_height) 11 | 12 | val body = new StaticBody("StaticBox", box) 13 | if(restitution) body.setRestitution(1.0f) 14 | body.setPosition(init_coord.x, init_coord.y) 15 | 16 | def points = box.getPoints(body.getPosition, body.getRotation).map(v => Vec(v.getX, v.getY)) 17 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/physics/objects/DynaBox.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.physics.objects 2 | 3 | import com.github.dunnololda.scage.support.Vec 4 | import com.github.dunnololda.scage.support.physics.Physical 5 | import net.phys2d.raw.Body 6 | import net.phys2d.raw.shapes.Box 7 | import java.lang.Float 8 | import com.github.dunnololda.cli.AppProperties._ 9 | 10 | class DynaBox(init_coord:Vec, val box_width:Float, val box_height:Float, val box_mass:Float = 1, restitution:Boolean = property("physics.restitution", true)) extends Physical { 11 | val box = new Box(box_width, box_height) 12 | val body = new Body(box, box_mass) 13 | if(restitution) body.setRestitution(1.0f) 14 | body.setPosition(init_coord.x, init_coord.y) 15 | 16 | def points = box.getPoints(body.getPosition, body.getRotation).map(v => Vec(v.getX, v.getY)) 17 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/XInitThreadsCaller.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support 2 | 3 | import com.github.dunnololda.mysimplelogger.MySimpleLogger.MySimpleLogger 4 | 5 | object XInitThreadsCaller { 6 | def xInitThreads(scage_log: MySimpleLogger): Unit = { 7 | if ("sun.awt.X11.XToolkit" == System.getProperty("awt.toolkit")) { 8 | // in Windows: sun.awt.windows.WToolkit 9 | if ("32" == System.getProperty("sun.arch.data.model")) { 10 | scage_log.info("32 bit linux detected, performing XInitThreads() call to make multi-threading work by loading library libxx32.so") 11 | System.loadLibrary("xx32") 12 | } else if ("64" == System.getProperty("sun.arch.data.model")) { 13 | scage_log.info("64 bit linux detected, performing XInitThreads() call to make multi-threading work by loading library libxx64.so") 14 | System.loadLibrary("xx64") 15 | } else { 16 | scage_log.warn("linux of unknown arch detected, don't now how to perform XInitThreads() call, so perform nothing. Maybe the app will crash. Sorry.") 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/resources/interfaces/scagetest_interfaces_en.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Use WASD to move, 5 | Le[rft Mo[yuse \[Butto\]n to s[ghoo]t and 6 | Mous]e Wheel to zo]om 7 | 8 | 9 | 10 | Special [{GREEN}gre[{RED}en-re]d] строка 11 | Special \[{GREEN}gre[{RED}en-re]d\] строка 12 | Special [{GREEN}gre\[{RED}en-re\]d] строка 13 | Special \[{GREEN}gre\[{RED}en-re\]d\] строка 14 | 15 | 16 | Location: $0 17 | 18 | 19 | Point: $0 20 | 21 | 22 | FPS: $0 23 | 24 | 25 | 26 | F1: [gInp[rut te]xt] 27 | 28 | 29 | Your text: $0 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/resources/resources/interfaces/scagetest_interfaces_ru.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WASD и Ле[rвая К[yн\[bопк\]а Мы\\[gши, 5 | Кол\\]есико - пр]иблизить/отд]алить. 6 | 7 | 8 | 9 | Cпециальная [{GREEN}зел[{RED}ено-кра]сная] строка 10 | Cпециальная \[{GREEN}зел[{RED}ено-кра]сная\] строка 11 | Cпециальная [{GREEN}зел\[{RED}ено-кра\]сная] строка 12 | Cпециальная \[{GREEN}зел\[{RED}ено-кра\]сная\] строка 13 | 14 | 15 | Позиция: $0 16 | 17 | 18 | Точка: $0 19 | 20 | 21 | FPS: $0 22 | 23 | 24 | 25 | F1: [gВве[rдите те]кст] 26 | 27 | 28 | Your text: $0 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/physics/objects/StaticPolygon.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.physics.objects 2 | 3 | import com.github.dunnololda.scage.support.Vec 4 | import com.github.dunnololda.scage.support.physics.Physical 5 | import net.phys2d.raw.shapes.Polygon 6 | import net.phys2d.raw.StaticBody 7 | import com.github.dunnololda.cli.AppProperties._ 8 | 9 | class StaticPolygon(restitution:Boolean, vertices:List[Vec]) extends Physical { 10 | def this(vertices:Vec*) {this(property("physics.restitution", true), vertices.toList)} 11 | def this(vertices:Array[Vec]) {this(property("physics.restitution", true), vertices.toList)} 12 | def this(vertices:List[Vec]) {this(property("physics.restitution", true), vertices)} 13 | 14 | private val polygon_vertices = for { 15 | vertice <- vertices 16 | new_vertice = vertice - vertices(0) 17 | } yield new_vertice 18 | private val polygon = new Polygon(polygon_vertices.map(_.toPhys2dVec).toArray) 19 | 20 | val body = new StaticBody("StaticPolygon", polygon) 21 | if(restitution) body.setRestitution(1.0f) 22 | body.setPosition(vertices(0).x, vertices(0).y) 23 | 24 | def points = polygon.getVertices(body.getPosition, body.getRotation).map(v => Vec(v.getX, v.getY)) 25 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/PathFinder.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support 2 | 3 | import org.newdawn.slick.util.pathfinding.{PathFindingContext, TileBasedMap, AStarPathFinder} 4 | import collection.mutable 5 | 6 | class PathFinder(width:Int, height:Int, val is_blocked:(Int, Int) => Boolean = (x, y) => false, val cost:(Int, Int) => Float = (x, y) => 1f) { 7 | private val slick_astar_path_finder = new AStarPathFinder(new TileBasedMap { 8 | def getWidthInTiles = width 9 | def getHeightInTiles = height 10 | def pathFinderVisited(x:Int, y:Int) {} 11 | def blocked(context:PathFindingContext, tx:Int, ty:Int) = is_blocked(tx, ty) 12 | def getCost(context:PathFindingContext, tx:Int, ty:Int) = cost(tx, ty) 13 | }, 100500, true) 14 | 15 | def findPath(p1:Vec, p2:Vec) = { 16 | val slick_path = slick_astar_path_finder.findPath(null, p1.ix, p1.iy, p2.ix, p2.iy) 17 | if(slick_path != null) mutable.Stack((for(i <- 0 until slick_path.getLength) yield Vec(slick_path.getX(i), slick_path.getY(i))):_*) 18 | else mutable.Stack[Vec]() 19 | } 20 | } 21 | 22 | object PathFinder { 23 | def apply(width:Int, height:Int, is_blocked:(Int, Int) => Boolean = (x, y) => false, cost:(Int, Int) => Float = (x, y) => 1f) = { 24 | new PathFinder(width, height, is_blocked, cost) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/tracer3/Trace.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.tracer3 2 | 3 | import com.github.dunnololda.scage.support.ScageId._ 4 | import com.github.dunnololda.scage.support.Vec 5 | import com.github.dunnololda.state.State 6 | 7 | trait TraceTrait { 8 | type ChangerType <: TraceTrait // changer type must be the type of actual Trace's child in client code 9 | def changeState(changer:ChangerType, state:State) // maybe 'changeState' is not the right name.. 10 | def state:State 11 | 12 | final val id = nextId // make it final because it is very important to keep it completely unique! 13 | private[tracer3] var _location = Vec(0, 0) 14 | def location:Vec = _location 15 | 16 | override def toString = getClass.getName+"(id="+id+", state="+state+")" 17 | } 18 | 19 | trait Trace extends TraceTrait { 20 | type ChangerType = Trace 21 | } 22 | 23 | trait DefaultTrace extends Trace { 24 | def state = State() 25 | def changeState(changer:Trace, state:State) {} 26 | } 27 | 28 | object Trace { 29 | def apply(changeState:(Trace, State) => Unit = (changer, state) => {}, state: => State = State()) = { 30 | val (_changeState, _state) = (changeState, state) 31 | new Trace { 32 | def changeState(changer:Trace, state:State) {_changeState(changer, state)} 33 | def state:State = _state 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/unicode/ConfigurableEffect.java: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages.unicode; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * An effect that has a number of configuration values. This allows the effect to be configured in the Hiero GUI and to be saved 7 | * and loaded to and from a file. 8 | * 9 | * @author Nathan Sweet 10 | */ 11 | public interface ConfigurableEffect extends Effect { 12 | /** 13 | * Returns the list of {@link Value}s for this effect. This list is not typically backed by the effect, so changes to the 14 | * values will not take affect until {@link #setValues(List)} is called. 15 | */ 16 | public List getValues(); 17 | 18 | /** 19 | * Sets the list of {@link Value}s for this effect. 20 | */ 21 | public void setValues(List values); 22 | 23 | /** 24 | * Represents a configurable value for an effect. 25 | */ 26 | static public interface Value { 27 | /** 28 | * Returns the name of the value. 29 | */ 30 | public String getName (); 31 | 32 | /** 33 | * Sets the string representation of the value. 34 | */ 35 | public void setString (String value); 36 | 37 | /** 38 | * Gets the string representation of the value. 39 | */ 40 | public String getString (); 41 | 42 | /** 43 | * Gets the object representation of the value. 44 | */ 45 | public Object getObject (); 46 | 47 | /** 48 | * Shows a dialog allowing a user to configure this value. 49 | */ 50 | public void showDialog (); 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/ScageColorTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support 2 | 3 | import com.github.dunnololda.scage.ScageLib._ 4 | 5 | object ScageColorTest extends ScageScreenApp("Color Test") { 6 | val fields = ScageColor.getClass.getDeclaredFields 7 | val colors = fields.map(f => { 8 | f.setAccessible(true) 9 | try{f.get(ScageColor).asInstanceOf[ScageColor]} 10 | catch { 11 | case ex:Exception => WHITE 12 | } 13 | }) 14 | 15 | var color_num = 1 16 | interface { 17 | if(color_num >= 0 && color_num < fields.length) { 18 | print(colors(color_num), 20, windowHeight/2, 19 | if("BLACK".equalsIgnoreCase(fields(color_num).getName)) WHITE else BLACK) 20 | try {backgroundColor = colors(color_num)} 21 | catch { 22 | case ex:java.lang.Exception => 23 | } 24 | } 25 | } 26 | 27 | key(KEY_LEFT, onKeyDown = { 28 | def nextColorNumInc() { 29 | if(color_num < fields.length - 1) color_num += 1 30 | else color_num = 0 31 | if(colors(color_num) != null && (!WHITE.equals(colors(color_num)) || "WHITE".equalsIgnoreCase(fields(color_num).getName))) {} 32 | else nextColorNumInc() 33 | } 34 | nextColorNumInc() 35 | }) 36 | key(KEY_RIGHT, onKeyDown = { 37 | def nextColorNumDec() { 38 | if(color_num > 0) color_num -= 1 39 | else color_num = fields.length - 1 40 | if(!WHITE.equals(colors(color_num)) || "WHITE".equalsIgnoreCase(fields(color_num).getName)) {} 41 | else nextColorNumDec() 42 | } 43 | nextColorNumDec() 44 | }) 45 | key(KEY_ESCAPE, onKeyDown = stop()) 46 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/parsers/VecParser.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.parsers 2 | 3 | import util.parsing.combinator.JavaTokenParsers 4 | import com.github.dunnololda.scage.support.{DVec, Vec} 5 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 6 | 7 | class VecParser extends JavaTokenParsers { 8 | private val log = MySimpleLogger(this.getClass.getName) 9 | 10 | private lazy val vec:Parser[Vec] = 11 | "["~floatingPointNumber~","~floatingPointNumber~"]" ^^ {case "["~x~","~y~"]" => new Vec(x.toFloat, y.toFloat)} 12 | 13 | def evaluate(vec_str:String) = parseAll(vec, vec_str) match { 14 | case Success(result, _) => 15 | log.debug("successfully parsed "+result+" from string "+vec_str) 16 | Some(result) 17 | case x @ Failure(msg, _) => 18 | log.error("failed to parse Vec from stirng "+vec_str) 19 | None 20 | case x @ Error(msg, _) => 21 | log.error("failed to parse Vec from stirng "+vec_str) 22 | None 23 | } 24 | } 25 | 26 | class DVecParser extends JavaTokenParsers { 27 | private val log = MySimpleLogger(this.getClass.getName) 28 | 29 | private lazy val dvec:Parser[DVec] = 30 | "["~floatingPointNumber~","~floatingPointNumber~"]" ^^ {case "["~x~","~y~"]" => new DVec(x.toDouble, y.toDouble)} 31 | 32 | def evaluate(vec_str:String) = parseAll(dvec, vec_str) match { 33 | case Success(result, _) => 34 | log.debug("successfully parsed "+result+" from string "+vec_str) 35 | Some(result) 36 | case x @ Failure(msg, _) => 37 | log.error("failed to parse Vec from stirng "+vec_str) 38 | None 39 | case x @ Error(msg, _) => 40 | log.error("failed to parse Vec from stirng "+vec_str) 41 | None 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/unicode/ColorEffect.java: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages.unicode; 2 | 3 | import org.newdawn.slick.font.effects.EffectUtil; 4 | import java.awt.*; 5 | import java.awt.image.BufferedImage; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Makes glyphs a solid color. 11 | * 12 | * @author Nathan Sweet 13 | */ 14 | public class ColorEffect implements ConfigurableEffect { 15 | /** The colour that will be applied across the text */ 16 | private Color color = Color.white; 17 | 18 | /** 19 | * Default constructor for injection 20 | */ 21 | public ColorEffect() { 22 | } 23 | 24 | /** 25 | * Create a new effect to colour the text 26 | * 27 | * @param color The colour to apply across the text 28 | */ 29 | public ColorEffect(Color color) { 30 | this.color = color; 31 | } 32 | 33 | /** 34 | * @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph) 35 | */ 36 | public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) { 37 | g.setColor(color); 38 | g.fill(glyph.getShape()); 39 | } 40 | 41 | /** 42 | * Get the colour being applied by this effect 43 | * 44 | * @return The colour being applied by this effect 45 | */ 46 | public Color getColor() { 47 | return color; 48 | } 49 | 50 | /** 51 | * Set the colour being applied by this effect 52 | * 53 | * @param color The colour being applied by this effect 54 | */ 55 | public void setColor(Color color) { 56 | if (color == null) throw new IllegalArgumentException("color cannot be null."); 57 | this.color = color; 58 | } 59 | 60 | /** 61 | * @see java.lang.Object#toString() 62 | */ 63 | public String toString () { 64 | return "Color"; 65 | } 66 | 67 | /** 68 | * @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues() 69 | */ 70 | public List getValues() { 71 | List values = new ArrayList(); 72 | values.add(EffectUtil.colorValue("Color", color)); 73 | return values; 74 | } 75 | 76 | /** 77 | * @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List) 78 | */ 79 | public void setValues(List values) { 80 | for (Object value1 : values) { 81 | Value value = (Value) value1; 82 | if (value.getName().equals("Color")) { 83 | setColor((Color) value.getObject()); 84 | } 85 | } 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/unicode/MyImage.java: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages.unicode; 2 | 3 | import org.newdawn.slick.SlickException; 4 | 5 | public class MyImage extends org.newdawn.slick.Image 6 | { 7 | public MyImage() 8 | { 9 | super(); 10 | } 11 | 12 | public MyImage(int width, int height) throws SlickException 13 | { 14 | super(width, height); 15 | } 16 | 17 | @Override protected void initImpl() 18 | { 19 | 20 | } 21 | 22 | public void drawEmbedded(float x, float y, float width, float height, int size) 23 | { 24 | init(); 25 | if (corners == null) { 26 | GL.glTexCoord2f(textureOffsetX, textureOffsetY); 27 | GL.glVertex3f(x, -y + size, 0); 28 | 29 | GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); 30 | GL.glVertex3f(x, -y - height + size, 0); 31 | 32 | GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + textureHeight); 33 | GL.glVertex3f(x + width, -y - height + size, 0); 34 | 35 | GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); 36 | GL.glVertex3f(x + width, -y + size, 0); 37 | } else { 38 | corners[TOP_LEFT].bind(); 39 | GL.glTexCoord2f(textureOffsetX, textureOffsetY); 40 | GL.glVertex3f(x, y, 0); 41 | 42 | corners[BOTTOM_LEFT].bind(); 43 | GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); 44 | GL.glVertex3f(x, y + height, 0); 45 | 46 | corners[BOTTOM_RIGHT].bind(); 47 | GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + textureHeight); 48 | GL.glVertex3f(x + width, y + height, 0); 49 | 50 | corners[TOP_RIGHT].bind(); 51 | GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); 52 | GL.glVertex3f(x + width, y, 0); 53 | 54 | 55 | 56 | 57 | } 58 | } 59 | 60 | @Override public MyImage getSubImage(int x,int y,int width,int height) { 61 | init(); 62 | 63 | float newTextureOffsetX = ((x / (float) this.width) * textureWidth) + textureOffsetX; 64 | float newTextureOffsetY = ((y / (float) this.height) * textureHeight) + textureOffsetY; 65 | float newTextureWidth = ((width / (float) this.width) * textureWidth); 66 | float newTextureHeight = ((height / (float) this.height) * textureHeight); 67 | 68 | MyImage sub = new MyImage(); 69 | sub.inited = true; 70 | sub.texture = this.texture; 71 | sub.textureOffsetX = newTextureOffsetX; 72 | sub.textureOffsetY = newTextureOffsetY; 73 | sub.textureWidth = newTextureWidth; 74 | sub.textureHeight = newTextureHeight; 75 | 76 | sub.width = width; 77 | sub.height = height; 78 | sub.ref = ref; 79 | sub.centerX = width / 2; 80 | sub.centerY = height / 2; 81 | 82 | return sub; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/physics/Physical.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.physics 2 | 3 | import net.phys2d.math.Vector2f 4 | import com.github.dunnololda.scage.support.Vec 5 | import net.phys2d.raw.{CollisionEvent, Body, BodyList} 6 | import collection.mutable.ArrayBuffer 7 | 8 | trait Physical { 9 | def body:Body 10 | 11 | def addForce(force:Vec) { 12 | body.setIsResting(false) 13 | body.addForce(new Vector2f(force.x, force.y)) 14 | } 15 | 16 | def coord = { 17 | val pos = body.getPosition 18 | Vec(pos.getX, pos.getY) 19 | } 20 | def coord_=(new_coord:Vec) { 21 | body.move(new_coord.x, new_coord.y) 22 | } 23 | def move(delta:Vec) { 24 | val new_coord = coord + delta 25 | body.move(new_coord.x, new_coord.y) 26 | } 27 | 28 | def velocity = { 29 | val vel = body.getVelocity 30 | Vec(vel.getX, vel.getY) 31 | } 32 | def velocity_=(new_velocity:Vec) { 33 | val delta = new_velocity - velocity 34 | body.adjustVelocity(new Vector2f(delta.x, delta.y)) 35 | } 36 | 37 | private var is_touching = false 38 | 39 | private val touching_bodies = ArrayBuffer[Body]() 40 | def touchingBodies = touching_bodies.toList 41 | 42 | private val touching_points = ArrayBuffer[(Vec, Vec)]() 43 | def isTouching = is_touching 44 | def isTouching(p:Physical) = touching_bodies.contains(p.body) 45 | 46 | private[physics] def clearTouches() { // will remove private modifier if needed 47 | is_touching = false 48 | touching_bodies.clear() 49 | touching_points.clear() 50 | } 51 | 52 | private[physics] def updateCollisions(collisions:Array[CollisionEvent]) { 53 | val new_touching_bodies = body.getTouching 54 | is_touching = new_touching_bodies.size > 0 55 | if(is_touching) { 56 | for { 57 | i <- 0 until new_touching_bodies.size 58 | body = new_touching_bodies.get(i) 59 | if !touching_bodies.contains(body) 60 | } touching_bodies += body 61 | for { 62 | ce <- collisions 63 | new_tp = (new Vec(ce.getPoint), new Vec(ce.getNormal)) 64 | if !touching_points.exists(tp => tp._1 == new_tp._1 && tp._2 == new_tp._2) 65 | } touching_points += new_tp 66 | } 67 | } 68 | 69 | /*private[physics] def isTouching_=(new_is_touching:Boolean) { 70 | is_touching = new_is_touching 71 | if(is_touching) { 72 | val new_touching_bodies = body.getTouching 73 | for { 74 | i <- 0 until new_touching_bodies.size 75 | body = new_touching_bodies.get(i) 76 | if !touching_bodies.contains(body) 77 | } touching_bodies.add(body) 78 | } 79 | else touching_bodies.clear() 80 | }*/ 81 | 82 | def points:Array[Vec] 83 | 84 | def touchingPoints = touching_points.toList 85 | } -------------------------------------------------------------------------------- /project/Build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import io.Source 4 | import Classpaths.managedJars 5 | 6 | // All credits for some parts of the code go to philcali 7 | // @see https://github.com/philcali/sbt-lwjgl-plugin/ 8 | object ScageBuild extends Build { 9 | lazy val phys2DPatch = TaskKey[Unit]("phys2D-patch", "The phys2d dependency pom is broken. Patch aims to fix it") 10 | 11 | lazy val nativesExtract = TaskKey[Unit]("natives-extract", "Extracts LWJGL Native JAR file") 12 | 13 | def defineOs = System.getProperty("os.name").toLowerCase.take(3).toString match { 14 | case "lin" => ("linux", "so") 15 | case "mac" | "dar" => ("osx", "lib") 16 | case "win" => ("windows", "dll") 17 | case "sun" => ("solaris", "so") 18 | case _ => ("unknown", "") 19 | } 20 | 21 | lazy val baseSettings: Seq[Setting[_]] = Defaults.defaultSettings ++ Seq( 22 | phys2DPatch <<= (streams, ivyPaths) map { (s, ivys) => 23 | val base = ivys.ivyHome.getOrElse(Path.userHome / ".ivy2") 24 | 25 | val path = base / "cache" / "phys2d" / "phys2d" / "ivy-060408.xml" 26 | 27 | if (path.exists) { 28 | s.log.info("Patching %s ..." format(path)) 29 | val pattern = "zip".r 30 | val ivysource = Source.fromFile(path) 31 | val text = ivysource.getLines.mkString 32 | val writer = new java.io.FileWriter(path) 33 | writer.write(pattern.replaceAllIn(text, "jar")) 34 | writer.close 35 | s.log.info("Done.") 36 | } else { 37 | s.log.warn("Update might fail. This is expected.") 38 | s.log.warn("Please run update one more time.") 39 | } 40 | }, 41 | 42 | nativesExtract <<= (streams, classpathTypes, update) map { (s, ct, up) => 43 | val natives = managedJars(Compile, ct, up) map { _.data } find { (j) => 44 | j.getName.startsWith("lwjgl-platform") && j.getName.endsWith("%s.jar".format(defineOs._1)) 45 | } 46 | natives map { (e) => 47 | val target = file(".") / "libs" / "natives" 48 | s.log.info("Extracting LWJGL natives to " + target) 49 | IO.unzip(e, target) 50 | } getOrElse { 51 | s.log.warn("Unable to find LWJGL natives jar, try to update again.") 52 | } 53 | }, 54 | 55 | // TODO should run on "update" only. 56 | update <<= update dependsOn phys2DPatch, 57 | // TODO : UH ? 58 | run <<= run in Runtime dependsOn nativesExtract, 59 | fork := true, 60 | javaOptions ++= Seq("-Djava.library.path=%s".format(file(".") / "libs" / "natives"), "-DLWJGL_DISABLE_XRANDR=true"), 61 | javaOptions in Test ++= Seq("-Dapp.properties=scagetest-properties.txt") 62 | ) 63 | 64 | lazy val project = Project ( 65 | "scage", 66 | file ("."), 67 | settings = baseSettings 68 | ) 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/Events.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support 2 | 3 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 4 | import com.github.dunnololda.scage.support.ScageId._ 5 | 6 | import scala.collection.mutable 7 | 8 | trait EventsTrait { 9 | def onEventWithArguments(event_name: String)(event_action: PartialFunction[Any, Unit]):(String, Int) 10 | def onEvent(event_name: String)(event_action: => Unit):(String, Int) 11 | def callEvent(event_name: String, arg: Any) 12 | def callEvent(event_name: String) 13 | def delEvents(event_ids: (String, Int)*) 14 | } 15 | 16 | object Events extends EventsTrait { 17 | private val log = MySimpleLogger(this.getClass.getName) 18 | private val events = mutable.HashMap[String, mutable.HashMap[Int, PartialFunction[Any, Unit]]]() 19 | 20 | def onEventWithArguments(event_name: String)(event_action: PartialFunction[Any, Unit]):(String, Int) = { 21 | val event_id = nextId 22 | (events.get(event_name) match { 23 | case Some(events_for_name) => 24 | events_for_name += (event_id -> event_action) 25 | case None => events += (event_name -> mutable.HashMap(event_id -> event_action)) 26 | }): Unit // this fixes some very huge compilation problem (very slow compilation) 27 | (event_name, event_id) 28 | } 29 | 30 | def onEvent(event_name: String)(event_action: => Unit):(String, Int) = { 31 | val event_id = nextId 32 | (events.get(event_name) match { 33 | case Some(events_for_name) => events_for_name += (event_id -> { 34 | case _ => event_action 35 | }) 36 | case None => events += (event_name -> mutable.HashMap(event_id -> { 37 | case _ => event_action 38 | })) 39 | }): Unit 40 | (event_name, event_id) 41 | } 42 | 43 | def callEvent(event_name: String, arg: Any) { 44 | events.get(event_name) match { 45 | case Some(events_for_name) => 46 | for (event <- events_for_name.values) event(arg) // fail-fast if not matched! 47 | case None => //log.warn("event "+event_name+" not found") 48 | } 49 | } 50 | 51 | def callEvent(event_name: String) { 52 | events.get(event_name) match { 53 | case Some(events_for_name) => 54 | for (event <- events_for_name.values) event(()) // fail-fast if not matched! 55 | case None => //log.warn("event "+event_name+" not found") 56 | } 57 | } 58 | 59 | def delEvents(event_ids: (String, Int)*) { 60 | for ((event_name, event_id) <- event_ids) { 61 | events.get(event_name) match { 62 | case Some(events_for_name) => 63 | if (events_for_name.contains(event_id)) { 64 | events_for_name -= event_id 65 | log.debug("deleted event for name " + event_name + " with id " + event_id) 66 | } else { 67 | log.warn("event for name " + event_name + " with id " + event_id + " not found among events so wasn't deleted") 68 | } 69 | case None => 70 | log.warn("events for name " + event_name + " not found so event with id " + event_id + " wasn't deleted") 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/SortedBuffer.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support 2 | 3 | import com.github.dunnololda.scage.ScageOperation 4 | 5 | import scala.collection.generic.{Growable, Shrinkable} 6 | import scala.collection.{Set, mutable} 7 | 8 | class SortedBuffer(init_arr:ScageOperation*) extends Iterable[ScageOperation] with Growable[ScageOperation] with Shrinkable[ScageOperation] { 9 | private val m = new java.util.TreeMap[Int, java.util.TreeMap[Int, ScageOperation]] 10 | private val position_by_op_id = mutable.HashMap[Int, Int]() 11 | private var _length = 0 12 | 13 | def operationIdsSet:Set[Int] = position_by_op_id.keySet 14 | def operationIdsSeq:Seq[Int] = position_by_op_id.keys.toSeq 15 | def operationIdsList:List[Int] = position_by_op_id.keys.toList 16 | def operationIdsIterable:Iterable[Int] = position_by_op_id.keys 17 | 18 | init_arr.foreach(elem => { 19 | this += elem 20 | }) 21 | 22 | def +=(elem: ScageOperation): this.type = { 23 | val a = m.get(elem.position) // O(logN) 24 | if(a == null) { 25 | m.put(elem.position, new java.util.TreeMap[Int, ScageOperation] {put(elem.op_id, elem)}) // O(logN) 26 | } else { 27 | a.put(elem.op_id, elem) // O(logN) 28 | } 29 | position_by_op_id += elem.op_id -> elem.position // O(1) 30 | _length += 1 // O(1) 31 | this 32 | } 33 | 34 | def clear(): Unit = { 35 | m.clear() 36 | position_by_op_id.clear() 37 | _length = 0 38 | } 39 | 40 | def remove(op_id: Int): Option[ScageOperation] = { 41 | position_by_op_id.get(op_id) match { // O(1) 42 | case Some(position) => 43 | val a = m.get(position) // O(logN) 44 | if(a != null) { 45 | val ans = Option(a.remove(op_id)) 46 | position_by_op_id -= op_id // O(1) 47 | if(a.isEmpty) { 48 | m.remove(position) // O(logN) 49 | } 50 | _length -= 1 51 | ans 52 | } else None 53 | case None => 54 | None 55 | } 56 | } 57 | 58 | def -=(elem: ScageOperation): this.type = { 59 | this.remove(elem.op_id) 60 | this 61 | } 62 | 63 | def iterator: Iterator[ScageOperation] = if(m.isEmpty) { 64 | new Iterator[ScageOperation] { 65 | def hasNext: Boolean = false 66 | def next(): ScageOperation = throw new NoSuchElementException("next on empty iterator") 67 | } 68 | } else { 69 | new Iterator[ScageOperation] { 70 | private val buffers_iterator = m.values().iterator() 71 | private var current_buffer_iterator = buffers_iterator.next().values().iterator() 72 | 73 | def hasNext: Boolean = current_buffer_iterator.hasNext || buffers_iterator.hasNext 74 | 75 | def next(): ScageOperation = { 76 | if(current_buffer_iterator.hasNext) { 77 | current_buffer_iterator.next() 78 | } else { 79 | current_buffer_iterator = buffers_iterator.next().values().iterator 80 | current_buffer_iterator.next() 81 | } 82 | } 83 | } 84 | } 85 | 86 | def length: Int = _length 87 | } 88 | 89 | object SortedBuffer { 90 | def apply(init_arr:ScageOperation*) = new SortedBuffer(init_arr:_*) 91 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/physics/ScagePhysics.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.physics 2 | 3 | import net.phys2d.raw.World 4 | import com.github.dunnololda.cli.AppProperties._ 5 | import net.phys2d.math.Vector2f 6 | import net.phys2d.raw.strategies.QuadSpaceStrategy 7 | import com.github.dunnololda.scage.support.Vec 8 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 9 | import collection.mutable 10 | 11 | object ScagePhysics { 12 | def str2vec(s:String):Vec = { 13 | val coords = s.replace("[", "").replace("]", "").split(",") 14 | try { 15 | Vec(coords(0).trim.toFloat, coords(1).trim.toFloat) 16 | } catch { 17 | case e:Exception => Vec.zero 18 | } 19 | } 20 | 21 | def apply(dt:Int = property("physics.dt", 5), 22 | gravity:Vec = str2vec(property("physics.gravity", "[0, 0]"))) = { 23 | new ScagePhysics(dt, gravity) 24 | } 25 | } 26 | 27 | class ScagePhysics( 28 | val dt:Int = property("physics.dt", 5), // see exactly no purposes to make it changeable. If I find any - I will do it. 29 | val gravity:Vec = ScagePhysics.str2vec(property("physics.gravity", "[0, 0]")) 30 | ) { 31 | private val log = MySimpleLogger(this.getClass.getName) 32 | 33 | /*def dt = _dt 34 | def dt_=(new_dt:Int) { 35 | if(new_dt > 0) dt = new_dt 36 | else log.error("failed to update dt: must be more then zero but the value is "+new_dt) 37 | }*/ 38 | 39 | val world = new World(new Vector2f(gravity.x, gravity.y), 10, new QuadSpaceStrategy(20,10)) 40 | world.enableRestingBodyDetection(0.01f, 0.2f, 0.2f) 41 | 42 | private val _physicals = mutable.HashSet[Physical]() 43 | def physicals = _physicals.toList 44 | def addPhysical(physical:Physical) = { 45 | if(!world.getBodies.contains(physical.body)) world.add(physical.body) 46 | _physicals += physical 47 | physical.clearTouches() 48 | log.debug("added new physical "+physical) 49 | physical 50 | } 51 | def addPhysicals(physicals:Physical*) { 52 | physicals.foreach(addPhysical) 53 | } 54 | 55 | // TODO: мб запилить по аналогии removePhysical, возвращающий, кого удалил. 56 | // TODO: И метод, принимающий условие в качестве параметра. И все такое 57 | def removePhysicals(physicals_to_delete:Physical*) { 58 | for(p <- physicals_to_delete) { 59 | if(_physicals.contains(p)) { 60 | world.remove(p.body) 61 | _physicals -= p 62 | log.debug("removed physical "+p) 63 | } else { 64 | log.warn("physical "+p+" not found") 65 | } 66 | } 67 | } 68 | def removeAll() { 69 | world.clear() 70 | _physicals.clear() 71 | log.info("deleted all physical objects") 72 | } 73 | 74 | def containsPhysical(p:Physical) = _physicals.contains(p) 75 | 76 | def step() { 77 | _physicals.foreach(_.clearTouches()) 78 | 79 | for(i <- 1 to dt) { 80 | world.step() 81 | for(p <- _physicals) { 82 | p.updateCollisions(world.getContacts(p.body)) 83 | } 84 | } 85 | } 86 | 87 | def touchingPoints(p:Physical) = { 88 | (for(ce <- world.getContacts(p.body)) yield { 89 | val phys2d_point= ce.getPoint 90 | val phys2d_normal = ce.getNormal 91 | (new Vec(phys2d_point), new Vec(phys2d_normal)) 92 | }).toList 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/unicode/Glyph.java: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages.unicode; 2 | 3 | import java.awt.*; 4 | import java.awt.font.GlyphMetrics; 5 | import java.awt.font.GlyphVector; 6 | 7 | /** 8 | * Represents the glyph in a font for a unicode codepoint. 9 | * 10 | * @author Nathan Sweet 11 | */ 12 | public class Glyph { 13 | /** The code point in which this glyph is found */ 14 | private int codePoint; 15 | /** The width of this glyph in pixels */ 16 | private short width; 17 | /** The height of this glyph in pixels */ 18 | private short height; 19 | /** The offset on the y axis to draw the glyph at */ 20 | private short yOffset; 21 | /** True if the glyph isn't defined */ 22 | private boolean isMissing; 23 | /** The shape drawn for this glyph */ 24 | private Shape shape; 25 | /** The image generated for this glyph */ 26 | private MyImage image; 27 | 28 | /** 29 | * Create a new glyph 30 | * 31 | * @param codePoint The code point in which this glyph can be found 32 | * @param bounds The bounds that this glrph can fill 33 | * @param vector The vector this glyph is part of 34 | * @param index The index of this glyph within the vector 35 | * @param unicodeFont The font this glyph forms part of 36 | */ 37 | public Glyph(int codePoint, Rectangle bounds, GlyphVector vector, int index, UnicodeFont unicodeFont) { 38 | this.codePoint = codePoint; 39 | 40 | GlyphMetrics metrics = vector.getGlyphMetrics(index); 41 | int lsb = (int)metrics.getLSB(); 42 | if (lsb > 0) lsb = 0; 43 | int rsb = (int)metrics.getRSB(); 44 | if (rsb > 0) rsb = 0; 45 | 46 | int glyphWidth = bounds.width - lsb - rsb; 47 | int glyphHeight = bounds.height; 48 | if (glyphWidth > 0 && glyphHeight > 0) { 49 | int padTop = unicodeFont.getPaddingTop(); 50 | int padRight = unicodeFont.getPaddingRight(); 51 | int padBottom = unicodeFont.getPaddingBottom(); 52 | int padLeft = unicodeFont.getPaddingLeft(); 53 | int glyphSpacing = 1; // Needed to prevent filtering problems. 54 | width = (short)(glyphWidth + padLeft + padRight + glyphSpacing); 55 | height = (short)(glyphHeight + padTop + padBottom + glyphSpacing); 56 | yOffset = (short)(unicodeFont.getAscent() + bounds.y - padTop); 57 | } 58 | 59 | shape = vector.getGlyphOutline(index, -bounds.x + unicodeFont.getPaddingLeft(), -bounds.y + unicodeFont.getPaddingTop()); 60 | 61 | isMissing = !unicodeFont.getFont().canDisplay((char)codePoint); 62 | } 63 | 64 | /** 65 | * The unicode codepoint the glyph represents. 66 | * 67 | * @return The codepoint the glyph represents 68 | */ 69 | public int getCodePoint () { 70 | return codePoint; 71 | } 72 | 73 | /** 74 | * Returns true if the font does not have a glyph for this codepoint. 75 | * 76 | * @return True if this glyph is not defined in the given code point 77 | */ 78 | public boolean isMissing () { 79 | return isMissing; 80 | } 81 | 82 | /** 83 | * The width of the glyph's image. 84 | * 85 | * @return The width in pixels of the glyphs image 86 | */ 87 | public int getWidth () { 88 | return width; 89 | } 90 | 91 | /** 92 | * The height of the glyph's image. 93 | * 94 | * @return The height in pixels of the glyphs image 95 | */ 96 | public int getHeight () { 97 | return height; 98 | } 99 | 100 | /** 101 | * The shape to use to draw this glyph. This is set to null after the glyph is stored 102 | * in a GlyphPage. 103 | * 104 | * @return The shape drawn for this glyph 105 | */ 106 | public Shape getShape () { 107 | return shape; 108 | } 109 | 110 | /** 111 | * Set the shape that should be drawn for this glyph 112 | * 113 | * @param shape The shape that should be drawn for this glyph 114 | */ 115 | public void setShape(Shape shape) { 116 | this.shape = shape; 117 | } 118 | 119 | /** 120 | * The image to use for this glyph. This is null until after the glyph is stored in a 121 | * GlyphPage. 122 | * 123 | * @return The image that has been generated for this glyph 124 | */ 125 | public MyImage getImage () { 126 | return image; 127 | } 128 | 129 | /** 130 | * Set the image that has been generated for this glyph 131 | * 132 | * @param image The image that has been generated for this glyph 133 | */ 134 | public void setImage(MyImage image) { 135 | this.image = image; 136 | this.image.rotate(180); 137 | } 138 | 139 | /** 140 | * The distance from drawing y location to top of this glyph, causing the glyph to sit 141 | * on the baseline. 142 | * 143 | * @return The offset on the y axis this glyph should be drawn at 144 | */ 145 | public int getYOffset() { 146 | return yOffset; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/ColoredString.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages 2 | 3 | import com.github.dunnololda.scage.support.ScageColor 4 | import com.github.dunnololda.scage.support.ScageColor._ 5 | import org.newdawn.slick.Color 6 | 7 | import scala.collection.mutable 8 | import scala.collection.mutable.ArrayBuffer 9 | import collection.JavaConversions._ 10 | 11 | /** 12 | * Этот класс представляет собой специальные строки, разные участки которых движок scage будет рисовать разными цветами. 13 | * Другой цвет для участка задается специальной разметкой вот так: "специальная строка с [rкрасным] участком" 14 | * то есть, участок отделяется квадратными скобками и сразу за открывающей квадратной скобкой ставится символ нужного цвета. 15 | * Поддерживается вложенность: "специальная [gзел[rено-кра]сная] строка". 16 | * Если функционал перекрашивания не нужен, а нужно именно написать что то в квадратных скобках, то перед открывающей и закрывающей 17 | * скобками следует поставить слеш: "неизменяемая \[rстрока\]". 18 | * Поскольку оперировать одним символом не очень удобно (задано мало цветов), синтаксис разметки расширен: 19 | * вместо одного символа можно задавать полное имя цвета в фигурных скобках. Примеры: 20 | * "специальная строка с [{RED}красным] участком" 21 | * "специальная [{GREEN}зел[{RED}ено-кра]сная] строка" 22 | * @param original_text - текст с разметкой цветных участков 23 | * @param default_color - цвет по умолчанию, которым будут нарисованы участки вне разметки 24 | */ 25 | class ColoredString(original_text:String, default_color:ScageColor) { 26 | def this(original_text:String, c:Color) {this(original_text, new ScageColor(c))} 27 | 28 | private val color_switches = mutable.HashMap[Int, ScageColor]() 29 | private val new_text = ArrayBuffer[Char]() 30 | private val previous_colors = mutable.Stack[ScageColor]() 31 | private var pos_offset = 0 32 | 33 | def colorSwitches:java.util.Map[Int, ScageColor] = color_switches 34 | def originalText = original_text 35 | def text = new_text.mkString 36 | 37 | /** 38 | * метод вычисляет цвет в фигурных скобках 39 | * @return 40 | */ 41 | private def calculateColor(text_arr:Array[Char], left_bracket_pos:Int):Option[(ScageColor, Int)] = { 42 | val right_bracket_pos = text_arr.indexOf('}', left_bracket_pos) 43 | val color_name = text_arr.drop(left_bracket_pos+1).take(right_bracket_pos-1 - left_bracket_pos).mkString 44 | ScageColor.fromString(color_name) match { 45 | case Some(color) => Some((color, right_bracket_pos - left_bracket_pos + 2)) 46 | case None => None 47 | } 48 | } 49 | 50 | private var is_previous_slash = false 51 | private def findColorSwitches(text_arr:Array[Char], pos:Int = 0, current_color:ScageColor = default_color) { 52 | if(0 <= pos && pos < text_arr.length) { 53 | text_arr(pos) match { 54 | case '\\' => 55 | if(!is_previous_slash) { 56 | is_previous_slash = true 57 | pos_offset += 1 58 | if(pos < text_arr.length-1) findColorSwitches(text_arr, pos+1, current_color) 59 | } else { 60 | is_previous_slash = false 61 | new_text += text_arr(pos) 62 | if(pos < text_arr.length-1) findColorSwitches(text_arr, pos+1, current_color) 63 | } 64 | case '[' if !is_previous_slash && pos < text_arr.length-1 => 65 | (text_arr(pos+1) match { 66 | case 'r' => Some((RED, 2)) 67 | case 'g' => Some((GREEN, 2)) 68 | case 'b' => Some((BLUE, 2)) 69 | case 'y' => Some((YELLOW, 2)) 70 | case 'o' => Some((ORANGE, 2)) 71 | case 'p' => Some((PURPLE, 2)) 72 | case 'm' => Some((MAGENTA, 2)) 73 | case 'c' => Some((CYAN, 2)) 74 | case '{' => calculateColor(text_arr, pos+1) 75 | case _ => None 76 | }) match { 77 | case Some((color, offset)) => 78 | color_switches += ((pos - pos_offset) -> color) 79 | pos_offset += offset 80 | previous_colors.push(current_color) 81 | if(pos < text_arr.length-offset) findColorSwitches(text_arr, pos+offset, color) 82 | case _ => 83 | new_text += text_arr(pos) 84 | findColorSwitches(text_arr, pos+1, current_color) 85 | } 86 | case ']' if !is_previous_slash => 87 | val previous_color = if(previous_colors.length > 0) previous_colors.pop() else default_color 88 | color_switches += ((pos - pos_offset) -> previous_color) 89 | pos_offset += 1 90 | if(pos < text_arr.length-1) findColorSwitches(text_arr, pos+1, previous_color) 91 | case _ => 92 | is_previous_slash = false 93 | new_text += text_arr(pos) 94 | if(pos < text_arr.length-1) findColorSwitches(text_arr, pos+1, current_color) 95 | } 96 | } 97 | } 98 | findColorSwitches(original_text.toCharArray) 99 | 100 | override def toString = s"ColoredString($original_text, $text, $color_switches)" 101 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/LWJGLKeyboard.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support 2 | 3 | import org.lwjgl.input.Keyboard 4 | 5 | 6 | object LWJGLKeyboard extends LWJGLKeyboard 7 | 8 | trait LWJGLKeyboard { 9 | val MOUSE_LEFT_BUTTON = 0 10 | val MOUSE_RIGHT_BUTTON = 1 11 | val MOUSE_MIDDLE_BUTTON = 2 // need a test to prove that 12 | 13 | val KEY_ESCAPE = Keyboard.KEY_ESCAPE 14 | val KEY_1 = Keyboard.KEY_1 15 | val KEY_2 = Keyboard.KEY_2 16 | val KEY_3 = Keyboard.KEY_3 17 | val KEY_4 = Keyboard.KEY_4 18 | val KEY_5 = Keyboard.KEY_5 19 | val KEY_6 = Keyboard.KEY_6 20 | val KEY_7 = Keyboard.KEY_7 21 | val KEY_8 = Keyboard.KEY_8 22 | val KEY_9 = Keyboard.KEY_9 23 | val KEY_0 = Keyboard.KEY_0 24 | val KEY_MINUS = Keyboard.KEY_MINUS 25 | val KEY_EQUALS = Keyboard.KEY_EQUALS 26 | val KEY_BACK = Keyboard.KEY_BACK 27 | val KEY_TAB = Keyboard.KEY_TAB 28 | val KEY_Q = Keyboard.KEY_Q 29 | val KEY_W = Keyboard.KEY_W 30 | val KEY_E = Keyboard.KEY_E 31 | val KEY_R = Keyboard.KEY_R 32 | val KEY_T = Keyboard.KEY_T 33 | val KEY_Y = Keyboard.KEY_Y 34 | val KEY_U = Keyboard.KEY_U 35 | val KEY_I = Keyboard.KEY_I 36 | val KEY_O = Keyboard.KEY_O 37 | val KEY_P = Keyboard.KEY_P 38 | val KEY_LBRACKET = Keyboard.KEY_LBRACKET 39 | val KEY_RBRACKET = Keyboard.KEY_RBRACKET 40 | val KEY_RETURN = Keyboard.KEY_RETURN 41 | val KEY_LCONTROL = Keyboard.KEY_LCONTROL 42 | val KEY_A = Keyboard.KEY_A 43 | val KEY_S = Keyboard.KEY_S 44 | val KEY_D = Keyboard.KEY_D 45 | val KEY_F = Keyboard.KEY_F 46 | val KEY_G = Keyboard.KEY_G 47 | val KEY_H = Keyboard.KEY_H 48 | val KEY_J = Keyboard.KEY_J 49 | val KEY_K = Keyboard.KEY_K 50 | val KEY_L = Keyboard.KEY_L 51 | val KEY_SEMICOLON = Keyboard.KEY_SEMICOLON 52 | val KEY_APOSTROPHE = Keyboard.KEY_APOSTROPHE 53 | val KEY_GRAVE = Keyboard.KEY_GRAVE 54 | val KEY_LSHIFT = Keyboard.KEY_LSHIFT 55 | val KEY_BACKSLASH = Keyboard.KEY_BACKSLASH 56 | val KEY_Z = Keyboard.KEY_Z 57 | val KEY_X = Keyboard.KEY_X 58 | val KEY_C = Keyboard.KEY_C 59 | val KEY_V = Keyboard.KEY_V 60 | val KEY_B = Keyboard.KEY_B 61 | val KEY_N = Keyboard.KEY_N 62 | val KEY_M = Keyboard.KEY_M 63 | val KEY_COMMA = Keyboard.KEY_COMMA 64 | val KEY_PERIOD = Keyboard.KEY_PERIOD 65 | val KEY_SLASH = Keyboard.KEY_SLASH 66 | val KEY_RSHIFT = Keyboard.KEY_RSHIFT 67 | val KEY_MULTIPLY = Keyboard.KEY_MULTIPLY 68 | val KEY_LMENU = Keyboard.KEY_LMENU 69 | val KEY_SPACE = Keyboard.KEY_SPACE 70 | val KEY_CAPITAL = Keyboard.KEY_CAPITAL 71 | val KEY_F1 = Keyboard.KEY_F1 72 | val KEY_F2 = Keyboard.KEY_F2 73 | val KEY_F3 = Keyboard.KEY_F3 74 | val KEY_F4 = Keyboard.KEY_F4 75 | val KEY_F5 = Keyboard.KEY_F5 76 | val KEY_F6 = Keyboard.KEY_F6 77 | val KEY_F7 = Keyboard.KEY_F7 78 | val KEY_F8 = Keyboard.KEY_F8 79 | val KEY_F9 = Keyboard.KEY_F9 80 | val KEY_F10 = Keyboard.KEY_F10 81 | val KEY_NUMLOCK = Keyboard.KEY_NUMLOCK 82 | val KEY_SCROLL = Keyboard.KEY_SCROLL 83 | val KEY_NUMPAD7 = Keyboard.KEY_NUMPAD7 84 | val KEY_NUMPAD8 = Keyboard.KEY_NUMPAD8 85 | val KEY_NUMPAD9 = Keyboard.KEY_NUMPAD9 86 | val KEY_SUBTRACT = Keyboard.KEY_SUBTRACT 87 | val KEY_NUMPAD4 = Keyboard.KEY_NUMPAD4 88 | val KEY_NUMPAD5 = Keyboard.KEY_NUMPAD5 89 | val KEY_NUMPAD6 = Keyboard.KEY_NUMPAD6 90 | val KEY_ADD = Keyboard.KEY_ADD 91 | val KEY_NUMPAD1 = Keyboard.KEY_NUMPAD1 92 | val KEY_NUMPAD2 = Keyboard.KEY_NUMPAD2 93 | val KEY_NUMPAD3 = Keyboard.KEY_NUMPAD3 94 | val KEY_NUMPAD0 = Keyboard.KEY_NUMPAD0 95 | val KEY_DECIMAL = Keyboard.KEY_DECIMAL 96 | val KEY_F11 = Keyboard.KEY_F11 97 | val KEY_F12 = Keyboard.KEY_F12 98 | val KEY_F13 = Keyboard.KEY_F13 99 | val KEY_F14 = Keyboard.KEY_F14 100 | val KEY_F15 = Keyboard.KEY_F15 101 | val KEY_KANA = Keyboard.KEY_KANA 102 | val KEY_CONVERT = Keyboard.KEY_CONVERT 103 | val KEY_NOCONVERT = Keyboard.KEY_NOCONVERT 104 | val KEY_YEN = Keyboard.KEY_YEN 105 | val KEY_NUMPADEQUALS = Keyboard.KEY_NUMPADEQUALS 106 | val KEY_CIRCUMFLEX = Keyboard.KEY_CIRCUMFLEX 107 | val KEY_AT = Keyboard.KEY_AT 108 | val KEY_COLON = Keyboard.KEY_COLON 109 | val KEY_UNDERLINE = Keyboard.KEY_UNDERLINE 110 | val KEY_KANJI = Keyboard.KEY_KANJI 111 | val KEY_STOP = Keyboard.KEY_STOP 112 | val KEY_AX = Keyboard.KEY_AX 113 | val KEY_UNLABELED = Keyboard.KEY_UNLABELED 114 | val KEY_NUMPADENTER = Keyboard.KEY_NUMPADENTER 115 | val KEY_RCONTROL = Keyboard.KEY_RCONTROL 116 | val KEY_NUMPADCOMMA = Keyboard.KEY_NUMPADCOMMA 117 | val KEY_DIVIDE = Keyboard.KEY_DIVIDE 118 | val KEY_SYSRQ = Keyboard.KEY_SYSRQ 119 | val KEY_RMENU = Keyboard.KEY_RMENU 120 | val KEY_PAUSE = Keyboard.KEY_PAUSE 121 | val KEY_HOME = Keyboard.KEY_HOME 122 | val KEY_UP = Keyboard.KEY_UP 123 | val KEY_PRIOR = Keyboard.KEY_PRIOR 124 | val KEY_LEFT = Keyboard.KEY_LEFT 125 | val KEY_RIGHT = Keyboard.KEY_RIGHT 126 | val KEY_END = Keyboard.KEY_END 127 | val KEY_DOWN = Keyboard.KEY_DOWN 128 | val KEY_NEXT = Keyboard.KEY_NEXT 129 | val KEY_INSERT = Keyboard.KEY_INSERT 130 | val KEY_DELETE = Keyboard.KEY_DELETE 131 | val KEY_LMETA = Keyboard.KEY_LMETA 132 | val KEY_RMETA = Keyboard.KEY_RMETA 133 | val KEY_APPS = Keyboard.KEY_APPS 134 | val KEY_POWER = Keyboard.KEY_POWER 135 | val KEY_SLEEP = Keyboard.KEY_SLEEP 136 | } 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Scage Logo](https://github.com/dunnololda/scage/blob/master/scage-logo.png) 2 | 3 | Latest Stable Version 4 | --------------------- 5 | 6 | 11.4 7 | 8 | Introduction 9 | ------------ 10 | 11 | Scage is a framework to write simple 2D opengl games. It is written in [Scala](http://scala-lang.org/) and based on several java libraries: 12 | 13 | - [phys2d](https://code.google.com/p/phys2d/) as a physics engine 14 | - [lwjgl](http://lwjgl.org) as an opengl wrapper 15 | - [slick](https://code.google.com/p/phys2d/) as a resource and texture loader 16 | - 17 | 18 | The main purpose of this project is to give a convenient tool for game-developers to write a code of pure functionality without any boilerplate. 19 | 20 | Features 21 | -------- 22 | 23 | - Architecture similar to actors framework with different kinds of tasks executing on different stages of app lifecycle. Simililar to actors these tasks are anonymous functions, and you can add and remove them in runtime in any scope of your app. Its all singlethreaded, so you dont have to mess with messages. 24 | - Vast drawing library for any kinds of 2D opengl primitives. 25 | - Loading and utilizing fonts from ttf-files (based on 'Slick2D' api but with improvements). 26 | - i18n: loading strings and even the whole interfaces from xml files. Runtime language change. 27 | - Framework to build in-game interfaces from xml files of simple structure. 28 | - App settings can be specified in a text files as a key-value pairs (moved to the external project: [scala-cli](https://github.com/dunnololda/scala-cli)). Lots of engine options are set that way (alongside with the standard possibility to set them as parameters) allowing fine-tuning without app rebuilding. 29 | - Tracers framework: easy game objects tracking and interacting on a two-dimensional game map. 30 | - Lightweight wrapper upon phys2d engine. 31 | - Easy app building/deploing (as a standalone or via webstart) using maven infrastructure. 32 | - Multiple platforms support: Windows, Linux, Mac, Solaris (thanks to Java and lwjgl actually). Similar build process for any platform (with maven). 33 | - Client/server network api upon actors with simple text protocol based on json format (moved to the external project: [simple-net](https://github.com/dunnololda/simple-net)). 34 | 35 | Please read the project wiki and especially see [Examples](https://github.com/dunnololda/scage/wiki/Examples) page to learn more! 36 | 37 | Hello World Example 38 | ------------------- 39 | 40 | ###Rotating 'Hello World!' label 41 | 42 | import com.github.dunnololda.scage.ScageLib._ 43 | 44 | object HelloWorldExample extends ScageScreenApp("Scage App", 640, 480) { 45 | private var ang = 0f 46 | actionStaticPeriod(100) { 47 | ang += 5 48 | } 49 | 50 | backgroundColor = BLACK 51 | render { 52 | openglMove(windowSize/2) 53 | openglRotate(ang) 54 | print("Hello World!", Vec(-50, -5), GREEN) 55 | } 56 | } 57 | 58 | !['rotating "Hello World!" demo'](http://dunnololda.github.io/pics/rotating_hello.png) 59 | 60 | More examples 61 | ------------- 62 | 63 | See [Examples Page](https://github.com/dunnololda/scage/wiki/Examples) 64 | 65 | Engine brief description 66 | ------------------------ 67 | 68 | See [Engine methods overview](https://github.com/dunnololda/scage/wiki/Scage-methods-overview) 69 | 70 | Usage 71 | ------------ 72 | 73 | ###For Maven users 74 | 75 | You can use the scage archetype to create a new scage project stub: 76 | 77 | $ mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeGroupId=scage -DarchetypeArtifactId=project-archetype -DarchetypeVersion=11.4 -DarchetypeRepository=https://raw.githubusercontent.com/dunnololda/mvn-repo/master/ 78 | 79 | This utilize the maven's "archetype" feature - create a simple example project with all needed stuff. Answer some questions about its name and version and you are done. 80 | 81 | For example: 82 | groupId: mygroup 83 | artifactId: myartifact 84 | version: 0.1 85 | package mygroup.myartifact 86 | 87 | (these all names are not really important, you can choose anything) 88 | 89 | In the end type Y, hit 'enter' and the folder "myartifact" will be created. Inside will be a small application, ready to compile, run and deploy - simple Light Cycles game based on the Tron movie. 90 | 91 | To launch app from the project stub you can type: 92 | 93 | $ mvn clean test 94 | 95 | This project stub has two profiles in its pom.xml for the app building. To build a standalone app type in your console: 96 | 97 | $ mvn clean package -Pbuild -Dmaven.test.skip 98 | 99 | Or just: 100 | 101 | $ mvn clean package -Dmaven.test.skip 102 | 103 | as "build" is a default profile. 104 | 105 | To build a webstart app type: 106 | 107 | $ mvn clean package -Pwebstart -Dmaven.test.skip 108 | 109 | This command will create "jnlp" folder in "target". Then you can upload this folder to your host. 110 | 111 | More info you can find in the readme file inside the project's root. 112 | 113 | ###OpenJDK 114 | 115 | If you use OpenJDK (not Oracle JDK) you need to add the openjdk profile to all mvn commands above: 116 | 117 | $ mvn clean test -Popenjdk 118 | $ mvn clean package -Pbuild,openjdk 119 | $ mvn clean package -Pwebstart,openjdk 120 | 121 | Additionally you need to install the package "icedtea-web" (this is the name in Archlinux, in Ubuntu it should be something similar). 122 | 123 | ###Intellij IDEA 124 | 125 | You also can use some IDE with good Maven and Scala support (for example, [IntelliJ IDEA](http://www.jetbrains.com/idea/)). Here are the steps for IDEA: 126 | 127 | Download Intellij IDEA Community Edition from there: https://www.jetbrains.com/idea/download/ (it's free). 128 | 129 | Unzip it and run. Setup scala plugin. 130 | 131 | Then in the main menu click "Import Project" - choose the folder "myartifact". 132 | In the new window choose "Import project from external model" - "Maven". Then just hit "Next" several times. 133 | 134 | Then wait for a while and IDEA will setup the project for you. 135 | 136 | How to run it: 137 | 138 | In the left panel go to src/main/scala/mygroup.myartifact. There is only one file - LightCyclesOffline.scala. Open it. Place the cursor in the row second to "object LightCyclesOffline extends ScageScreenApp("Light Cycles", 640, 480) {". 139 | 140 | Right click - Create LightCyclesOffline. In the new window in the "VM Options" type: 141 | 142 | -Djava.library.path=target/natives -DLWJGL_DISABLE_XRANDR=true -Dfile.encoding=UTF-8 143 | 144 | Then click OK. Then for example right click again and choose Run LightCyclesOffline. IDEA will build and run the app. 145 | 146 | If you see 147 | 148 | "Exception in thread "main" java.lang.UnsatisfiedLinkError: no lwjgl in java.library.path" 149 | 150 | or 151 | 152 | "Exception in thread "main" java.lang.UnsatisfiedLinkError: no lwjgl64 in java.library.path" 153 | 154 | this means you have no native libraries in target/natives. In order to generate them, type some mvn command from above. For example, type "mvn clean compile". As a first step of the compilation process maven will generate those libraries in target/natives. 155 | 156 | ###For non-Maven users. 157 | 158 | You can both : 159 | - Compile Scage with SBT 160 | - Use SBT in your own Scage projects with [SBT Scage Plugin](https://github.com/mvallerie/sbt-scage-plugin). Just follow the README :). 161 | 162 | Feedback 163 | -------- 164 | 165 | Feel free to ask any questions by email or using issue tracker. 166 | 167 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/handlers/controller2/ScageController.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.handlers.controller2 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.Date 5 | 6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 7 | import com.github.dunnololda.scage.{ScagePhase, Scage} 8 | import com.github.dunnololda.scage.ScageLib.coordOnArea 9 | import com.github.dunnololda.scage.support.Vec 10 | import org.lwjgl.input.{Keyboard, Mouse} 11 | 12 | import scala.collection.mutable 13 | 14 | case class KeyPress(key_code: Int, var was_pressed: Boolean, var pressed_start_time: Long, private var last_pressed_time: Long) { 15 | def lastPressedTime: Long = last_pressed_time 16 | 17 | def updateLastPressedTime(new_time: Long): Unit = { 18 | last_pressed_time = new_time 19 | ScageController.updateMaxLastPressedTime(last_pressed_time) 20 | } 21 | 22 | def immutable: ImmutableKeyPress = ImmutableKeyPress(key_code, was_pressed, pressed_start_time, last_pressed_time) 23 | 24 | private val f = new SimpleDateFormat("HH:mm:ss.S") 25 | 26 | override def toString = s"KeyPress($key_code, $was_pressed, ${f.format(new Date(pressed_start_time))}, ${f.format(new Date(last_pressed_time))})" 27 | } 28 | 29 | case class ImmutableKeyPress(key_code: Int, was_pressed: Boolean, pressed_start_time: Long, last_pressed_time: Long) 30 | 31 | case class MouseButtonPress(button_code: Int, var was_pressed: Boolean, var pressed_start_time: Long, private var last_pressed_time: Long) { 32 | def lastPressedTime: Long = last_pressed_time 33 | 34 | def updateLastPressedTime(new_time: Long): Unit = { 35 | last_pressed_time = new_time 36 | ScageController.updateMaxLastPressedTime(last_pressed_time) 37 | } 38 | 39 | def immutable: ImmutableMouseButtonPress = ImmutableMouseButtonPress(button_code, was_pressed, pressed_start_time, last_pressed_time) 40 | 41 | private val f = new SimpleDateFormat("HH:mm:ss.S") 42 | 43 | override def toString = s"MouseButtonPress($button_code, $was_pressed, ${f.format(new Date(pressed_start_time))}, ${f.format(new Date(last_pressed_time))})" 44 | } 45 | 46 | case class ImmutableMouseButtonPress(button_code: Int, was_pressed: Boolean, pressed_start_time: Long, last_pressed_time: Long) 47 | 48 | object ScageController { 49 | private val key_presses = mutable.HashMap[Int, KeyPress]() 50 | private val mouse_button_presses = mutable.HashMap[Int, MouseButtonPress]() 51 | 52 | private var _max_last_pressed_time: Long = 0l 53 | 54 | private[scage] def updateMaxLastPressedTime(new_time: Long) { 55 | if (new_time > _max_last_pressed_time) { 56 | _max_last_pressed_time = new_time 57 | } 58 | } 59 | } 60 | 61 | trait ScageController extends Scage { 62 | private val log = MySimpleLogger(this.getClass.getName) 63 | 64 | protected def mappedKeyboardKeys: scala.collection.Set[Int] 65 | 66 | protected def mappedMouseButtons: scala.collection.Set[Int] 67 | 68 | private implicit class SeqLongRich(s: Iterable[Long]) { 69 | def maxOption: Option[Long] = if (s.nonEmpty) Some(s.max) else None 70 | } 71 | 72 | protected def maxLastPressedTime: Long = ScageController._max_last_pressed_time 73 | 74 | protected def innerKeyPress(key_code: Int): Option[KeyPress] = { 75 | ScageController.key_presses.get(key_code) match { 76 | case skp@Some(kp: KeyPress) => skp 77 | case None if mappedKeyboardKeys.contains(key_code) => 78 | val kp = KeyPress(key_code, was_pressed = false, 0L, 0L) 79 | ScageController.key_presses += (key_code -> kp) 80 | Some(kp) 81 | case _ => None 82 | } 83 | } 84 | 85 | def keyPress(key_code: Int): Option[ImmutableKeyPress] = innerKeyPress(key_code).map(_.immutable) 86 | 87 | protected def innerMouseButtonPress(mouse_button: Int): Option[MouseButtonPress] = { 88 | ScageController.mouse_button_presses.get(mouse_button) match { 89 | case smbp@Some(mbp: MouseButtonPress) => smbp 90 | case None if mappedMouseButtons.contains(mouse_button) => 91 | val mbp = MouseButtonPress(mouse_button, was_pressed = false, 0L, 0L) 92 | ScageController.mouse_button_presses += (mouse_button -> mbp) 93 | Some(mbp) 94 | case _ => None 95 | } 96 | } 97 | 98 | def mouseButtonPress(key_code: Int): Option[ImmutableMouseButtonPress] = innerMouseButtonPress(key_code).map(_.immutable) 99 | 100 | def keyPressed(key_code: Int): Boolean = { 101 | /*val KeyPress(_, was_pressed, _) = keyPress(key_code) 102 | was_pressed*/ 103 | Keyboard.isKeyDown(key_code) 104 | } 105 | 106 | def leftMousePressed: Boolean = { 107 | /*val MouseButtonPress(_, was_pressed, _) = mouseButtonPress(0) 108 | was_pressed*/ 109 | Mouse.isButtonDown(0) 110 | } 111 | 112 | def rightMousePressed: Boolean = { 113 | /*val MouseButtonPress(_, was_pressed, _) = mouseButtonPress(1) 114 | was_pressed*/ 115 | Mouse.isButtonDown(1) 116 | } 117 | 118 | def mouseOnArea(area: List[Vec]): Boolean = { 119 | coordOnArea(mouseCoord, area) 120 | } 121 | 122 | def key(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}): Int 123 | 124 | def keyIgnorePause(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}): Int 125 | 126 | def keyOnPause(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}): Int 127 | 128 | def anykey(onKeyDown: => Any): Int 129 | 130 | def anykeyIgnorePause(onKeyDown: => Any): Int 131 | 132 | def anykeyOnPause(onKeyDown: => Any): Int 133 | 134 | def mouseCoord: Vec 135 | 136 | def isMouseMoved: Boolean 137 | 138 | def leftMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int 139 | 140 | def leftMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int 141 | 142 | def leftMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int 143 | 144 | def rightMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int 145 | 146 | def rightMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int 147 | 148 | def rightMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int 149 | 150 | def mouseMotion(onMotion: Vec => Any): Int 151 | 152 | def mouseMotionIgnorePause(onMotion: Vec => Any): Int 153 | 154 | def mouseMotionOnPause(onMotion: Vec => Any): Int 155 | 156 | def leftMouseDrag(onDrag: Vec => Any): Int 157 | 158 | def leftMouseDragIgnorePause(onDrag: Vec => Any): Int 159 | 160 | def leftMouseDragOnPause(onDrag: Vec => Any): Int 161 | 162 | def rightMouseDrag(onDrag: Vec => Any): Int 163 | 164 | def rightMouseDragIgnorePause(onDrag: Vec => Any): Int 165 | 166 | def rightMouseDragOnPause(onDrag: Vec => Any): Int 167 | 168 | def mouseWheelUp(onWheelUp: Vec => Any): Int 169 | 170 | def mouseWheelUpIgnorePause(onWheelUp: Vec => Any): Int 171 | 172 | def mouseWheelUpOnPause(onWheelUp: Vec => Any): Int 173 | 174 | def mouseWheelDown(onWheelDown: Vec => Any): Int 175 | 176 | def mouseWheelDownIgnorePause(onWheelDown: Vec => Any): Int 177 | 178 | def mouseWheelDownOnPause(onWheelDown: Vec => Any): Int 179 | 180 | private[scage] def checkControls() 181 | 182 | private[scage] val control_deletion_operations = defaultContainer("control_deleters", ScagePhase.Controls, execute_if_app_running = false, execute_on_deletion = true) 183 | 184 | def delControl(control_id: Int) = { 185 | delOp(control_id, show_warnings = true) 186 | } 187 | 188 | def delControls(control_ids: Int*) { 189 | control_ids.foreach(control_id => { 190 | delOp(control_id, show_warnings = true) 191 | }) 192 | } 193 | 194 | def delAllControls() { 195 | control_deletion_operations.operations.operationIdsIterable.foreach(control_id => { 196 | delOp(control_id, show_warnings = true) 197 | }) 198 | } 199 | 200 | def delAllControlsExcept(except_control_ids: Int*) { 201 | control_deletion_operations.operations.operationIdsIterable.filterNot(op_id => { 202 | except_control_ids.contains(op_id) 203 | }).foreach(control_id => { 204 | delOp(control_id, show_warnings = true) 205 | }) 206 | } 207 | } 208 | 209 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/Vec.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support 2 | 3 | import _root_.net.phys2d.math.{ROVector2f, Vector2f} 4 | import parsers.{DVecParser, VecParser} 5 | 6 | /** 7 | * I believe for now this glorious piece of code has all the needed to be the exact match to 'case Vec(x:Int, y:Int) {...}': 8 | * 1. lots of apply() methods 9 | * 2. one pretty cool unapply() method 10 | * 3. redefined equals(), hashCode() and canEquals() methods! (- that part was hard) 11 | */ 12 | object Vec { 13 | def apply(x:Float, y:Float) = new Vec(x, y) 14 | def apply(v:Vec) = v.copy 15 | def apply(v:ROVector2f) = new Vec(v.getX, v.getY) 16 | def apply(x:Double, y:Double) = new Vec(x.toFloat, y.toFloat) 17 | def apply() = new Vec(0, 0) 18 | 19 | def unapply(data:Any):Option[(Float, Float)] = data match { 20 | case v:Vec => Some((v.x, v.y)) 21 | case _ => None 22 | } 23 | 24 | private val vec_parser= new VecParser() 25 | def fromString(vec_str:String):Option[Vec] = vec_parser.evaluate(vec_str) 26 | def fromStringOrDefault(vec_str:String, default_vec:Vec = zero):Vec = vec_parser.evaluate(vec_str) match { 27 | case Some(v:Vec) => v 28 | case None => default_vec 29 | } 30 | 31 | lazy val zero = new Vec(0, 0) 32 | } 33 | 34 | class Vec(val x:Float = 0, val y:Float = 0) { 35 | lazy val ix = x.toInt 36 | lazy val iy = y.toInt 37 | 38 | def this(v:Vec) = this(v.x, v.y) 39 | def this(v:ROVector2f) = this(v.getX, v.getY) 40 | def this(x:Double, y:Double) = this(x.toFloat, y.toFloat) 41 | def this(x:Int, y:Int) = this(x.toFloat, y.toFloat) 42 | def this() = this(0,0) 43 | 44 | def +(v:Vec) = new Vec(x+v.x, y+v.y) 45 | def -(v:Vec) = new Vec(x-v.x, y-v.y) 46 | 47 | def unary_-():Vec = new Vec(-x, -y) 48 | 49 | def project(v:Vec):Vec = v*(this*v) 50 | 51 | def *(v:Vec):Float = x*v.x + y*v.y 52 | def */(v:Vec):Float = x*v.y - y*v.x // maybe another symbol as it is a closing comment symbol 53 | def */(k:Float):Vec = Vec(y*k, -x*k) 54 | 55 | def *(k:Double) = new Vec(x*k, y*k) 56 | def *(k:Float) = new Vec(x*k, y*k) 57 | def *(k:Int) = new Vec(x*k, y*k) 58 | 59 | def **(v:Vec) = new Vec(x*v.x, y*v.y) 60 | 61 | def /(k:Double):Vec = this / k.toFloat 62 | def /(k:Float):Vec = if(k == 0) Vec(x*1000, y*1000) else Vec(x/k, y/k) 63 | def /(k:Int):Vec = this / k.toFloat 64 | 65 | def norma2:Float = x*x + y*y 66 | def norma = math.sqrt(norma2).toFloat 67 | def n = this/norma 68 | 69 | def perpendicular = new Vec(-y, x) 70 | def p = perpendicular.n 71 | 72 | def dist2(v:Vec) = (x - v.x)*(x - v.x) + (y - v.y)*(y - v.y) 73 | def dist(v:Vec) = math.sqrt(dist2(v)).toFloat 74 | 75 | def notZero = x != 0 || y != 0 76 | def isZero = x == 0 && y == 0 77 | override def equals(other:Any):Boolean = other match { 78 | case that:Vec => (that canEqual this) && this.x == that.x && this.y == that.y 79 | case _ => false 80 | } 81 | override val hashCode:Int = (41*(41 + x) + y).toInt 82 | def canEqual(other: Any) = other.isInstanceOf[Vec] 83 | 84 | def absDeg(v:Vec):Float = math.acos(math.max(math.min(n * v.n, 1.0), -1.0)).toFloat/math.Pi.toFloat*180f 85 | def deg(v:Vec):Float = absDeg(v) 86 | def signedDeg(v:Vec):Float = { 87 | val scalar = perpendicular*v 88 | if(scalar >= 0) absDeg(v) else -absDeg(v) 89 | } 90 | 91 | def absRad(v:Vec) = math.acos(math.max(math.min(n * v.n, 1.0), -1.0)).toFloat 92 | def rad(v:Vec) = absRad(v) 93 | def signedRad(v:Vec):Float = { 94 | val scalar = perpendicular*v 95 | if(scalar >= 0) absRad(v) else -absRad(v) 96 | } 97 | 98 | def rotateRad(ang_rad:Double) = new Vec((x * math.cos(ang_rad) - y * math.sin(ang_rad)).toFloat, 99 | (x * math.sin(ang_rad) + y * math.cos(ang_rad)).toFloat) 100 | def rotate(ang_rad:Double) = rotateRad(ang_rad) 101 | def rotateDeg(ang_deg:Double) = rotateRad(ang_deg/180*math.Pi) 102 | 103 | def copy = new Vec(x, y) 104 | def copy(x:Float = x, y:Float = y) = new Vec(x, y) 105 | 106 | def toDVec = new DVec(x, y) 107 | 108 | def toPhys2dVec = new Vector2f(x, y) 109 | 110 | override def toString = s"Vec($x, $y)" 111 | 112 | def map[A](f:(Vec) => A):A = f(this) 113 | } 114 | 115 | object DVec { 116 | def apply(x:Float, y:Float) = new DVec(x, y) 117 | def apply(dv:DVec) = dv.copy 118 | def apply(v:ROVector2f) = new DVec(v.getX, v.getY) 119 | def apply(x:Double, y:Double) = new DVec(x, y) 120 | def apply() = new DVec(0, 0) 121 | 122 | def unapply(data:Any):Option[(Double, Double)] = data match { 123 | case dv:DVec => Some((dv.x, dv.y)) 124 | case _ => None 125 | } 126 | 127 | private lazy val dvec_parser = new DVecParser() 128 | def fromString(vec_str:String):Option[DVec] = dvec_parser.evaluate(vec_str) 129 | def fromStringOrDefault(vec_str:String, default_vec:DVec = dzero):DVec = dvec_parser.evaluate(vec_str) match { 130 | case Some(dv:DVec) => dv 131 | case None => default_vec 132 | } 133 | 134 | lazy val dzero = new DVec(0, 0) 135 | lazy val zero = new DVec(0, 0) 136 | } 137 | 138 | class DVec(val x:Double = 0, val y:Double = 0) { 139 | lazy val ix = x.toInt 140 | lazy val iy = y.toInt 141 | 142 | def this(dv:DVec) = this(dv.x, dv.y) 143 | def this(v:Vec) = this(v.x, v.y) 144 | def this(v:ROVector2f) = this(v.getX, v.getY) 145 | def this() = this(0,0) 146 | 147 | def +(dv:DVec) = new DVec(x+dv.x, y+dv.y) 148 | def -(dv:DVec) = new DVec(x-dv.x, y-dv.y) 149 | 150 | def unary_-():DVec = new DVec(-x, -y) 151 | 152 | def *(dv:DVec) = x*dv.x + y*dv.y 153 | def */(v:DVec) = x*v.y - y*v.x 154 | def */(k:Double):DVec = DVec(y*k, -x*k) 155 | 156 | def *(k:Double) = new DVec(x*k, y*k) 157 | def *(k:Float) = new DVec(x*k, y*k) 158 | def *(k:Int) = new DVec(x*k, y*k) 159 | 160 | def **(dv:DVec) = new DVec(x*dv.x, y*dv.y) 161 | 162 | def /(k:Double):DVec = if(k == 0) new DVec(x*1000, y*1000) else new DVec(x/k, y/k) 163 | def /(k:Float):DVec = this / k.toDouble 164 | def /(k:Int):DVec = this / k.toDouble 165 | 166 | def norma2:Double = x*x + y*y 167 | def norma = math.sqrt(norma2) 168 | def n = this/norma 169 | 170 | def perpendicular = new DVec(-y, x) 171 | def p = perpendicular.n 172 | 173 | def dist2(dv:DVec) = (x - dv.x)*(x - dv.x) + (y - dv.y)*(y - dv.y) 174 | def dist(dv:DVec) = math.sqrt(dist2(dv)) 175 | 176 | def notZero = x != 0 || y != 0 177 | def isZero = x == 0 && y == 0 178 | override def equals(other:Any):Boolean = other match { 179 | case that:DVec => (that canEqual this) && this.x == that.x && this.y == that.y 180 | case _ => false 181 | } 182 | override val hashCode:Int = (41*(41 + x) + y).toInt 183 | def canEqual(other: Any) = other.isInstanceOf[DVec] 184 | 185 | def absDeg(dv:DVec) = 180 / math.Pi * math.acos(math.max(math.min(n * dv.n, 1.0), -1.0)) 186 | def deg(dv:DVec) = absDeg(dv) 187 | def signedDeg(dv:DVec) = { 188 | val scalar = perpendicular*dv 189 | if(scalar >= 0) absDeg(dv) else -absDeg(dv) 190 | } 191 | 192 | def absRad(dv:DVec) = math.acos(math.max(math.min(n * dv.n, 1.0), -1.0)) 193 | def rad(dv:DVec) = absRad(dv) 194 | def signedRad(dv:DVec) = { 195 | val scalar = perpendicular*dv 196 | if(scalar >= 0) absRad(dv) else -absRad(dv) 197 | } 198 | 199 | def rotateRad(ang_rad:Double) = new DVec(x * math.cos(ang_rad) - y * math.sin(ang_rad), 200 | x * math.sin(ang_rad) + y * math.cos(ang_rad)) 201 | def rotate(ang_rad:Double) = rotateRad(ang_rad) 202 | def rotateDeg(ang_deg:Double) = rotateRad(ang_deg/180*math.Pi) 203 | 204 | def copy = new DVec(x, y) 205 | def copy(x:Double = x, y:Double = y) = new DVec(x, y) 206 | 207 | def toVec = new Vec(x, y) 208 | 209 | def toPhys2dVec = new Vector2f(x.toFloat, y.toFloat) 210 | 211 | override def toString = s"DVec($x, $y)" 212 | 213 | def map[A](f:(DVec) => A):A = f(this) 214 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/tracer3/CoordTracer.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.tracer3 2 | 3 | import com.github.dunnololda.scage.support.Vec 4 | import com.github.dunnololda.cli.AppProperties._ 5 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 6 | import com.github.dunnololda.scage.handlers.RendererLib 7 | 8 | class CoordTracer[T <: TraceTrait](field_from_x:Int = property("field.from.x", 0), 9 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}), 10 | field_from_y:Int = property("field.from.y", 0), 11 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}), 12 | init_h_x:Int = property("field.h_x", 0), 13 | init_h_y:Int = property("field.h_y", 0), 14 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0, 15 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0, 16 | solid_edges:Boolean = property("field.solid_edges", true)) 17 | extends ScageTracer[T](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges) { 18 | private val log = MySimpleLogger(this.getClass.getName) 19 | 20 | override def addTrace(coord:Vec, trace:T):T = { 21 | if(isCoordOnArea(coord)) { 22 | trace._location = coord 23 | val p = point(coord) 24 | point_matrix(p.ix)(p.iy) += trace 25 | traces_by_ids += trace.id -> trace 26 | traces_list += trace 27 | log.debug("added new trace #"+trace.id+" in coord "+trace.location) 28 | } else log.warn("failed to add trace: coord "+trace.location+" is out of area") 29 | trace 30 | } 31 | 32 | override protected def _removeTrace(trace_id:Int, show_warn:Boolean) { 33 | traces_by_ids.get(trace_id) match { 34 | case Some(trace) => 35 | val trace_point = point(trace.location) 36 | point_matrix(trace_point.ix)(trace_point.iy) -= trace 37 | traces_by_ids -= trace.id 38 | traces_list -= trace 39 | log.debug("removed trace #"+trace.id) 40 | case None => if(show_warn) log.warn("trace #"+trace_id+" not found") 41 | } 42 | } 43 | 44 | def tracesNearCoord(coord:Vec, xrange:Range, yrange:Range, condition:T => Boolean):IndexedSeq[T] = { 45 | val p = point(coord) 46 | super.tracesNearPoint(p, xrange, yrange, condition) 47 | } 48 | def tracesNearCoord(coord:Vec, xrange:Range, condition:T => Boolean):IndexedSeq[T] = tracesNearCoord(coord, xrange, xrange, condition) 49 | def tracesNearCoord(coord:Vec, xrange:Range, yrange:Range):IndexedSeq[T] = { 50 | val p = point(coord) 51 | super.tracesNearPoint(p, xrange, yrange) 52 | } 53 | def tracesNearCoord(coord:Vec, xrange:Range):IndexedSeq[T] = tracesNearCoord(coord:Vec, xrange:Range, xrange:Range) 54 | 55 | override def updateLocation(trace_id:Int, new_coord:Vec):Int = { // TODO: maybe return tuple (new_location, operation_status) 56 | traces_by_ids.get(trace_id) match { 57 | case Some(trace) => 58 | val old_coord = trace.location 59 | val old_point = point(old_coord) 60 | val new_coord_edges_affected = outsideCoord(new_coord) 61 | if(isCoordOnArea(new_coord_edges_affected)) { 62 | val new_point_edges_affected = point(new_coord_edges_affected) 63 | if(old_coord != new_coord_edges_affected) { 64 | point_matrix(old_point.ix)(old_point.iy) -= trace 65 | point_matrix(new_point_edges_affected.ix)(new_point_edges_affected.iy) += trace 66 | trace._location = new_coord_edges_affected 67 | LOCATION_UPDATED 68 | } else { 69 | //log.warn("didn't update trace "+trace.id+": new point is the same as the old one") // don'tknow exactly if I need such debug message 70 | SAME_LOCATION 71 | } 72 | } else { 73 | log.debug("failed to update trace "+trace_id+": new point is out of area") 74 | OUT_OF_AREA 75 | } 76 | case None => 77 | log.warn("trace with id "+trace_id+" not found") 78 | TRACE_NOT_FOUND 79 | } 80 | } 81 | 82 | def outsideCoord(coord:Vec):Vec = { 83 | def checkC(c:Float, from:Float, to:Float):Float = { 84 | val dist = to - from 85 | if(c >= to) checkC(c - dist, from, to) 86 | else if(c < from) checkC(c + dist, from, to) 87 | else c 88 | } 89 | 90 | if(solid_edges) coord else { 91 | val x = checkC(coord.x, field_from_x, field_to_x) 92 | val y = checkC(coord.y, field_from_y, field_to_y) 93 | 94 | Vec(x, y) 95 | } 96 | } 97 | 98 | def isCoordOnArea(coord:Vec):Boolean = { 99 | coord.x >= field_from_x && coord.x < field_to_x && coord.y >= field_from_y && coord.y < field_to_y 100 | } 101 | 102 | def hasCollisions(target_trace_id:Int, tested_coord:Vec, min_dist:Float, condition:T => Boolean = (trace) => true):Boolean = { 103 | if(solid_edges && !isCoordOnArea(tested_coord)) true 104 | else { 105 | val tested_coord_edges_affected = outsideCoord(tested_coord) 106 | val min_dist2 = min_dist*min_dist 107 | val modified_condition = (trace:T) => trace.id != target_trace_id && condition(trace) 108 | 109 | val xrange = (2*min_dist/h_x).toInt + 1 110 | val yrange = (2*min_dist/h_y).toInt + 1 111 | tracesNearCoord(tested_coord_edges_affected, -xrange to xrange, -yrange to yrange, modified_condition) 112 | .exists(trace => (trace.location dist2 tested_coord_edges_affected) < min_dist2) 113 | } 114 | } 115 | } 116 | 117 | object CoordTracer { 118 | def apply(field_from_x:Int = property("field.from.x", 0), 119 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}), 120 | field_from_y:Int = property("field.from.y", 0), 121 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}), 122 | init_h_x:Int = property("field.h_x", 0), 123 | init_h_y:Int = property("field.h_y", 0), 124 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0, 125 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0, 126 | solid_edges:Boolean = property("field.solid_edges", true)) = { 127 | new CoordTracer[Trace](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges) { 128 | def addTrace(coord:Vec):Trace = {addTrace(coord, Trace())} 129 | } 130 | } 131 | 132 | // maybe some other name for this factory method (like 'newTracer', etc) 133 | def create[T <: TraceTrait](field_from_x:Int = property("field.from.x", 0), 134 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}), 135 | field_from_y:Int = property("field.from.y", 0), 136 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}), 137 | init_h_x:Int = property("field.h_x", 0), 138 | init_h_y:Int = property("field.h_y", 0), 139 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0, 140 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0, 141 | solid_edges:Boolean = property("field.solid_edges", true)) = { 142 | new CoordTracer[T](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges) 143 | } 144 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/unicode/GlyphPage.java: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages.unicode; 2 | 3 | import org.lwjgl.opengl.GL11; 4 | import org.lwjgl.opengl.GL12; 5 | import org.newdawn.slick.Color; 6 | import org.newdawn.slick.Image; 7 | import org.newdawn.slick.SlickException; 8 | import org.newdawn.slick.opengl.TextureImpl; 9 | import org.newdawn.slick.opengl.renderer.Renderer; 10 | import org.newdawn.slick.opengl.renderer.SGL; 11 | 12 | import java.awt.*; 13 | import java.awt.font.FontRenderContext; 14 | import java.awt.image.BufferedImage; 15 | import java.awt.image.WritableRaster; 16 | import java.nio.ByteBuffer; 17 | import java.nio.ByteOrder; 18 | import java.nio.IntBuffer; 19 | import java.util.ArrayList; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | import java.util.ListIterator; 23 | 24 | /** 25 | * Stores a number of glyphs on a single texture. 26 | * 27 | * @author Nathan Sweet 28 | */ 29 | public class GlyphPage { 30 | /** The interface to OpenGL */ 31 | private static final SGL GL = Renderer.get(); 32 | 33 | /** The maxium size of an individual glyph */ 34 | public static final int MAX_GLYPH_SIZE = 256; 35 | 36 | /** A temporary working buffer */ 37 | private static ByteBuffer scratchByteBuffer = ByteBuffer.allocateDirect(MAX_GLYPH_SIZE * MAX_GLYPH_SIZE * 4); 38 | 39 | static { 40 | scratchByteBuffer.order(ByteOrder.LITTLE_ENDIAN); 41 | } 42 | 43 | /** A temporary working buffer */ 44 | private static IntBuffer scratchIntBuffer = scratchByteBuffer.asIntBuffer(); 45 | 46 | 47 | /** A temporary image used to generate the glyph page */ 48 | private static BufferedImage scratchImage = new BufferedImage(MAX_GLYPH_SIZE, MAX_GLYPH_SIZE, BufferedImage.TYPE_INT_ARGB); 49 | /** The graphics context form the temporary image */ 50 | private static Graphics2D scratchGraphics = (Graphics2D)scratchImage.getGraphics(); 51 | 52 | static { 53 | scratchGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 54 | scratchGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 55 | scratchGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); 56 | } 57 | 58 | /** The render context in which the glyphs will be generated */ 59 | public static FontRenderContext renderContext = scratchGraphics.getFontRenderContext(); 60 | 61 | /** 62 | * Get the scratch graphics used to generate the page of glyphs 63 | * 64 | * @return The scratch graphics used to build the page 65 | */ 66 | public static Graphics2D getScratchGraphics() { 67 | return scratchGraphics; 68 | } 69 | 70 | /** The font this page is part of */ 71 | private final UnicodeFont unicodeFont; 72 | /** The width of this page's image */ 73 | private final int pageWidth; 74 | /** The height of this page's image */ 75 | private final int pageHeight; 76 | /** The image containing the glyphs */ 77 | private final MyImage pageImage; 78 | /** The x position of the page */ 79 | private int pageX; 80 | /** The y position of the page */ 81 | private int pageY; 82 | /** The height of the last row on the page */ 83 | private int rowHeight; 84 | /** True if the glyphs are ordered */ 85 | private boolean orderAscending; 86 | /** The list of glyphs on this page */ 87 | private final List pageGlyphs = new ArrayList(32); 88 | 89 | /** 90 | * Create a new page of glyphs 91 | * 92 | * @param unicodeFont The font this page forms part of 93 | * @param pageWidth The width of the backing texture. 94 | * @param pageHeight The height of the backing texture. 95 | * @throws SlickException if the backing texture could not be created. 96 | */ 97 | public GlyphPage(UnicodeFont unicodeFont, int pageWidth, int pageHeight) throws SlickException { 98 | this.unicodeFont = unicodeFont; 99 | this.pageWidth = pageWidth; 100 | this.pageHeight = pageHeight; 101 | 102 | pageImage = new MyImage(pageWidth, pageHeight); 103 | } 104 | 105 | /** 106 | * Loads glyphs to the backing texture and sets the image on each loaded glyph. Loaded glyphs are removed from the list. 107 | * 108 | * If this page already has glyphs and maxGlyphsToLoad is -1, then this method will return 0 if all the new glyphs don't fit. 109 | * This reduces texture binds when drawing since glyphs loaded at once are typically displayed together. 110 | * @param glyphs The glyphs to load. 111 | * @param maxGlyphsToLoad This is the maximum number of glyphs to load from the list. Set to -1 to attempt to load all the 112 | * glyphs. 113 | * @return The number of glyphs that were actually loaded. 114 | * @throws SlickException if the glyph could not be rendered. 115 | */ 116 | public int loadGlyphs (List glyphs, int maxGlyphsToLoad) throws SlickException { 117 | if (rowHeight != 0 && maxGlyphsToLoad == -1) { 118 | // If this page has glyphs and we are not loading incrementally, return zero if any of the glyphs don't fit. 119 | int testX = pageX; 120 | int testY = pageY; 121 | int testRowHeight = rowHeight; 122 | for (Iterator iter = getIterator(glyphs); iter.hasNext();) { 123 | Glyph glyph = (Glyph)iter.next(); 124 | int width = glyph.getWidth(); 125 | int height = glyph.getHeight(); 126 | if (testX + width >= pageWidth) { 127 | testX = 0; 128 | testY += testRowHeight; 129 | testRowHeight = height; 130 | } else if (height > testRowHeight) { 131 | testRowHeight = height; 132 | } 133 | if (testY + testRowHeight >= pageWidth) return 0; 134 | testX += width; 135 | } 136 | } 137 | 138 | Color.white.bind(); 139 | pageImage.bind(); 140 | 141 | int i = 0; 142 | for (Iterator iter = getIterator(glyphs); iter.hasNext();) { 143 | Glyph glyph = (Glyph)iter.next(); 144 | int width = Math.min(MAX_GLYPH_SIZE, glyph.getWidth()); 145 | int height = Math.min(MAX_GLYPH_SIZE, glyph.getHeight()); 146 | 147 | if (rowHeight == 0) { 148 | // The first glyph always fits. 149 | rowHeight = height; 150 | } else { 151 | // Wrap to the next line if needed, or break if no more fit. 152 | if (pageX + width >= pageWidth) { 153 | if (pageY + rowHeight + height >= pageHeight) break; 154 | pageX = 0; 155 | pageY += rowHeight; 156 | rowHeight = height; 157 | } else if (height > rowHeight) { 158 | if (pageY + height >= pageHeight) break; 159 | rowHeight = height; 160 | } 161 | } 162 | 163 | renderGlyph(glyph, width, height); 164 | pageGlyphs.add(glyph); 165 | 166 | pageX += width; 167 | 168 | iter.remove(); 169 | i++; 170 | if (i == maxGlyphsToLoad) { 171 | // If loading incrementally, flip orderAscending so it won't change, since we'll probably load the rest next time. 172 | orderAscending = !orderAscending; 173 | break; 174 | } 175 | } 176 | 177 | TextureImpl.bindNone(); 178 | 179 | // Every other batch of glyphs added to a page are sorted the opposite way to attempt to keep same size glyps together. 180 | orderAscending = !orderAscending; 181 | 182 | return i; 183 | } 184 | 185 | /** 186 | * Loads a single glyph to the backing texture, if it fits. 187 | * 188 | * @param glyph The glyph to be rendered 189 | * @param width The expected width of the glyph 190 | * @param height The expected height of the glyph 191 | * @throws SlickException if the glyph could not be rendered. 192 | */ 193 | private void renderGlyph(Glyph glyph, int width, int height) throws SlickException { 194 | // Draw the glyph to the scratch image using Java2D. 195 | scratchGraphics.setComposite(AlphaComposite.Clear); 196 | scratchGraphics.fillRect(0, 0, MAX_GLYPH_SIZE, MAX_GLYPH_SIZE); 197 | scratchGraphics.setComposite(AlphaComposite.SrcOver); 198 | scratchGraphics.setColor(java.awt.Color.white); 199 | for (Object o : unicodeFont.getEffects()) ((Effect) o).draw(scratchImage, scratchGraphics, unicodeFont, glyph); 200 | glyph.setShape(null); // The shape will never be needed again. 201 | 202 | WritableRaster raster = scratchImage.getRaster(); 203 | int[] row = new int[width]; 204 | for (int y = 0; y < height; y++) { 205 | raster.getDataElements(0, y, width, 1, row); 206 | scratchIntBuffer.put(row); 207 | } 208 | GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, pageX, pageY, width, height, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, 209 | scratchByteBuffer); 210 | scratchIntBuffer.clear(); 211 | 212 | glyph.setImage(pageImage.getSubImage(pageX, pageY, width, height)); 213 | } 214 | 215 | /** 216 | * Returns an iterator for the specified glyphs, sorted either ascending or descending. 217 | * 218 | * @param glyphs The glyphs to return if present 219 | * @return An iterator of the sorted list of glyphs 220 | */ 221 | private Iterator getIterator(List glyphs) { 222 | if (orderAscending) return glyphs.iterator(); 223 | final ListIterator iter = glyphs.listIterator(glyphs.size()); 224 | return new Iterator() { 225 | public boolean hasNext () { 226 | return iter.hasPrevious(); 227 | } 228 | 229 | public Object next () { 230 | return iter.previous(); 231 | } 232 | 233 | public void remove () { 234 | iter.remove(); 235 | } 236 | }; 237 | } 238 | 239 | /** 240 | * Returns the glyphs stored on this page. 241 | * 242 | * @return A list of {@link Glyph} elements on this page 243 | */ 244 | public List getGlyphs () { 245 | return pageGlyphs; 246 | } 247 | 248 | /** 249 | * Returns the backing texture for this page. 250 | * 251 | * @return The image of this page of glyphs 252 | */ 253 | public Image getImage () { 254 | return pageImage; 255 | } 256 | } 257 | 258 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/handlers/controller2/SingleController.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.handlers.controller2 2 | 3 | import org.lwjgl.input.{Keyboard, Mouse} 4 | import collection.mutable 5 | import com.github.dunnololda.scage.support.Vec 6 | 7 | case class SingleKeyEvent(key_code:Int, repeat_time: () => Long, onKeyDown: () => Any, onKeyUp: () => Any) 8 | case class SingleMouseButtonEvent(button_code:Int, repeat_time: () => Long, onButtonDown: Vec => Any, onButtonUp: Vec => Any) 9 | case class WindowButtonEvent(area:List[Vec], button_code:Int, repeat_time: () => Long, onButtonDown: Vec => Any, onButtonUp: Vec => Any) 10 | 11 | trait SingleController extends ScageController { 12 | private val keyboard_key_events = mutable.HashMap[Int, SingleKeyEvent]() // was_pressed, last_pressed_time, repeat_time, onKeyDown, onKeyUp 13 | private var anykey: () => Any = () => {} 14 | private val mouse_button_events = mutable.HashMap[Int, SingleMouseButtonEvent]() 15 | private var on_mouse_motion: Vec => Any = v => {} 16 | private val on_mouse_drag_motion = mutable.HashMap[Int, Vec => Any]() 17 | private var on_mouse_wheel_up: Vec => Any = v => {} 18 | private var on_mouse_wheel_down: Vec => Any = v => {} 19 | 20 | protected def mappedKeyboardKeys:scala.collection.Set[Int] = keyboard_key_events.keySet 21 | protected def mappedMouseButtons:scala.collection.Set[Int] = mouse_button_events.keySet 22 | 23 | def key(key_code:Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}) = { 24 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => if(!on_pause) onKeyDown, () => if(!on_pause) onKeyUp) 25 | control_deletion_operations.addOp(() => keyboard_key_events -= key_code, 0) 26 | } 27 | def keyIgnorePause(key_code:Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}) = { 28 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => onKeyDown, () => onKeyUp) 29 | control_deletion_operations.addOp(() => keyboard_key_events -= key_code, 0) 30 | } 31 | def keyOnPause(key_code:Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}):Int = { 32 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => if(on_pause) onKeyDown, () => if(on_pause) onKeyUp) 33 | control_deletion_operations.addOp(() => keyboard_key_events -= key_code, 0) 34 | } 35 | 36 | def anykey(onKeyDown: => Any) = { 37 | anykey = () => if(!on_pause) onKeyDown 38 | control_deletion_operations.addOp(() => anykey = () => {}, 0) 39 | } 40 | def anykeyIgnorePause(onKeyDown: => Any) = { 41 | anykey = () => onKeyDown 42 | control_deletion_operations.addOp(() => anykey = () => {}, 0) 43 | } 44 | def anykeyOnPause(onKeyDown: => Any) = { 45 | anykey = () => if(on_pause) onKeyDown 46 | control_deletion_operations.addOp(() => anykey = () => {}, 0) 47 | } 48 | 49 | def mouseCoord = Vec(Mouse.getX, Mouse.getY) 50 | def isMouseMoved = Mouse.getDX != 0 || Mouse.getDY != 0 51 | private def mouseButton(button_code:Int, repeat_time: => Long = 0, onButtonDown: Vec => Any, onButtonUp: Vec => Any = Vec => {}) = { 52 | mouse_button_events(button_code) = SingleMouseButtonEvent(button_code, () => repeat_time, onButtonDown, onButtonUp) 53 | control_deletion_operations.addOp(() => mouse_button_events -= button_code, 0) 54 | } 55 | 56 | def leftMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 57 | mouseButton(0, repeat_time, mouse_coord => if(!on_pause) onBtnDown(mouse_coord), mouse_coord => if(!on_pause) onBtnUp(mouse_coord)) 58 | } 59 | def leftMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 60 | mouseButton(0, repeat_time, onBtnDown, onBtnUp) 61 | } 62 | def leftMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 63 | mouseButton(0, repeat_time, mouse_coord => if(on_pause) onBtnDown(mouse_coord), mouse_coord => if(on_pause) onBtnUp(mouse_coord)) 64 | } 65 | 66 | def rightMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 67 | mouseButton(1, repeat_time, mouse_coord => if(!on_pause) onBtnDown(mouse_coord), mouse_coord => if(!on_pause) onBtnUp(mouse_coord)) 68 | } 69 | def rightMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 70 | mouseButton(1, repeat_time, onBtnDown, onBtnUp) 71 | } 72 | def rightMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 73 | mouseButton(1, repeat_time, mouse_coord => if(on_pause) onBtnDown(mouse_coord), mouse_coord => if(on_pause) onBtnUp(mouse_coord)) 74 | } 75 | 76 | def mouseMotion(onMotion: Vec => Any) = { 77 | on_mouse_motion = mouse_coord => if(!on_pause) onMotion(mouse_coord) 78 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0) 79 | } 80 | def mouseMotionIgnorePause(onMotion: Vec => Any) = { 81 | on_mouse_motion = onMotion 82 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0) 83 | } 84 | def mouseMotionOnPause(onMotion: Vec => Any) = { 85 | on_mouse_motion = mouse_coord => if(on_pause) onMotion(mouse_coord) 86 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0) 87 | } 88 | 89 | private def mouseDrag(button_code:Int, onDrag: Vec => Any) = { 90 | on_mouse_drag_motion(button_code) = onDrag 91 | control_deletion_operations.addOp(() => on_mouse_drag_motion -= button_code, 0) 92 | } 93 | 94 | def leftMouseDrag(onDrag: Vec => Any) = { 95 | mouseDrag(0, mouse_coord => if(!on_pause) onDrag(mouse_coord)) 96 | } 97 | def leftMouseDragIgnorePause(onDrag: Vec => Any) = { 98 | mouseDrag(0, onDrag) 99 | } 100 | def leftMouseDragOnPause(onDrag: Vec => Any) = { 101 | mouseDrag(0, mouse_coord => if(on_pause) onDrag(mouse_coord)) 102 | } 103 | 104 | def rightMouseDrag(onDrag: Vec => Any) = { 105 | mouseDrag(1, mouse_coord => if(!on_pause) onDrag(mouse_coord)) 106 | } 107 | def rightMouseDragIgnorePause(onDrag: Vec => Any) = { 108 | mouseDrag(1, onDrag) 109 | } 110 | def rightMouseDragOnPause(onDrag: Vec => Any) = { 111 | mouseDrag(1, mouse_coord => if(on_pause) onDrag(mouse_coord)) 112 | } 113 | 114 | def mouseWheelUp(onWheelUp: Vec => Any) = { 115 | on_mouse_wheel_up = mouse_coord => if(!on_pause) onWheelUp(mouse_coord) 116 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0) 117 | } 118 | def mouseWheelUpIgnorePause(onWheelUp: Vec => Any) = { 119 | on_mouse_wheel_up = onWheelUp 120 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0) 121 | } 122 | def mouseWheelUpOnPause(onWheelUp: Vec => Any) = { 123 | on_mouse_wheel_up = mouse_coord => if(on_pause) onWheelUp(mouse_coord) 124 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0) 125 | } 126 | 127 | def mouseWheelDown(onWheelDown: Vec => Any) = { 128 | on_mouse_wheel_down = mouse_coord => if(!on_pause) onWheelDown(mouse_coord) 129 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0) 130 | } 131 | def mouseWheelDownIgnorePause(onWheelDown: Vec => Any) = { 132 | on_mouse_wheel_down = onWheelDown 133 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0) 134 | } 135 | def mouseWheelDownOnPause(onWheelDown: Vec => Any) = { 136 | on_mouse_wheel_down = mouse_coord => if(on_pause) onWheelDown(mouse_coord) 137 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0) 138 | } 139 | 140 | def checkControls() { 141 | for { 142 | (key, key_data) <- keyboard_key_events 143 | SingleKeyEvent(_, repeat_time_func, onKeyDown, onKeyUp) = key_data 144 | key_press @ KeyPress(_, was_pressed, _, last_pressed_time) <- innerKeyPress(key) 145 | } { 146 | if(Keyboard.isKeyDown(key)) { 147 | val repeat_time = repeat_time_func() 148 | val is_repeatable = repeat_time > 0 149 | if(!was_pressed || (is_repeatable && System.currentTimeMillis() - last_pressed_time > repeat_time)) { 150 | if(!key_press.was_pressed) key_press.pressed_start_time = System.currentTimeMillis() 151 | key_press.was_pressed = true 152 | key_press.updateLastPressedTime(System.currentTimeMillis()) 153 | onKeyDown() 154 | } 155 | } else if(was_pressed) { 156 | key_press.was_pressed = false 157 | onKeyUp() 158 | } 159 | } 160 | 161 | if(Keyboard.next && Keyboard.getEventKeyState) anykey() 162 | 163 | val mouse_coord = mouseCoord 164 | val is_mouse_moved = isMouseMoved 165 | if(is_mouse_moved) on_mouse_motion(mouse_coord) 166 | 167 | for { 168 | (button, button_data) <- mouse_button_events 169 | SingleMouseButtonEvent(_, repeat_time_func, onButtonDown, onButtonUp) = button_data 170 | mouse_button_press @ MouseButtonPress(_, was_pressed, _, last_pressed_time) <- innerMouseButtonPress(button) 171 | } { 172 | if(Mouse.isButtonDown(button)) { 173 | val repeat_time = repeat_time_func() 174 | val is_repeatable = repeat_time > 0 175 | if(!was_pressed || (is_repeatable && System.currentTimeMillis() - last_pressed_time > repeat_time)) { 176 | if(!mouse_button_press.was_pressed) mouse_button_press.pressed_start_time = System.currentTimeMillis() 177 | mouse_button_press.was_pressed = true 178 | mouse_button_press.updateLastPressedTime(System.currentTimeMillis()) 179 | onButtonDown(mouse_coord) 180 | } 181 | } else if(was_pressed) { 182 | mouse_button_press.was_pressed = false 183 | onButtonUp(mouse_coord) 184 | } 185 | } 186 | 187 | if(is_mouse_moved) { 188 | for { 189 | (button, onDragMotion) <- on_mouse_drag_motion 190 | if Mouse.isButtonDown(button) 191 | } onDragMotion(mouse_coord) 192 | } 193 | 194 | Mouse.getDWheel match { 195 | case x if x > 0 => on_mouse_wheel_up(mouse_coord) 196 | case x if x < 0 => on_mouse_wheel_down(mouse_coord) 197 | case _ => 198 | } 199 | } 200 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/ScageColor.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support 2 | 3 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 4 | 5 | import collection.mutable 6 | 7 | 8 | class ScageColor(val name:String, r:Float, g:Float, b:Float) { 9 | def this(r:Float, g:Float, b:Float) {this("Color", r, g, b)} 10 | def this(c:org.newdawn.slick.Color) {this(c.r, c.g, c.b)} 11 | val red:Float = if(r >= 0 && r <= 1) r else if(r > 1 && r < 256) r/256 else -1 12 | val green:Float = if(g >= 0 && g <= 1) g else if(g > 1 && g < 256) g/256 else -1 13 | val blue:Float = if(b >= 0 && b <= 1) b else if(b > 1 && b < 256) b/256 else -1 14 | 15 | override def equals(other:Any):Boolean = other match { 16 | case that:ScageColor => (that canEqual this) && this.red == that.red && this.green == that.green && this.blue == that.blue 17 | case _ => false 18 | } 19 | override val hashCode:Int = (41*(41*(41 + red) + green) + blue).toInt 20 | def canEqual(other: Any) = other.isInstanceOf[ScageColor] 21 | 22 | def toSlickColor = new org.newdawn.slick.Color(red, green, blue) 23 | 24 | override def toString = name+"(red="+red+" green="+green+" blue="+blue+")" 25 | } 26 | 27 | object ScageColor extends ScageColorTrait { 28 | private val log = MySimpleLogger(this.getClass.getName) 29 | 30 | def apply(name:String, r:Float, g:Float, b:Float) = new ScageColor(name, r, g, b) 31 | def apply(r:Float, g:Float, b:Float) = new ScageColor(r, g, b) 32 | 33 | def unapply(data:Any):Option[(String, Float, Float, Float)] = data match { 34 | case c:ScageColor => Some((c.name, c.red, c.green, c.blue)) 35 | case _ => None 36 | } 37 | 38 | } 39 | 40 | trait ScageColorTrait { 41 | val DEFAULT_COLOR = new ScageColor("Default_Color", -1, -1, -1) 42 | 43 | val RED: ScageColor = new ScageColor("Red", 1, 0, 0) 44 | val GREEN: ScageColor = new ScageColor("Green", 0, 1, 0) 45 | val BLUE: ScageColor = new ScageColor("Blue", 0, 0, 1) 46 | val CORNFLOWER: ScageColor = new ScageColor("Cornflower", 0.39f, 0.58f, 0.93f) 47 | val CYAN: ScageColor = new ScageColor("Cyan", 0, 1, 1) 48 | val YELLOW: ScageColor = new ScageColor("Yellow", 1, 1, 0) 49 | val WHITE: ScageColor = new ScageColor("White", 1, 1, 1) 50 | val GRAY: ScageColor = new ScageColor("Gray", 0x80, 0x80, 0x80) 51 | val BLACK: ScageColor = new ScageColor("Black", 0, 0, 0) 52 | 53 | val SNOW: ScageColor = new ScageColor("Snow", 0xFF, 0xFA, 0xFA) 54 | val GHOSTWHITE: ScageColor = new ScageColor("Ghostwhite", 0xF8, 0xF8, 0xFF) 55 | val ANTIQUE_WHITE: ScageColor = new ScageColor("Antique_White", 0xFA, 0xEB, 0xD7) 56 | val CREAM: ScageColor = new ScageColor("Cream", 0xFF, 0xFB, 0xF0) 57 | val PEACHPUFF: ScageColor = new ScageColor("Peachpuff", 0xFF, 0xDA, 0xB9) 58 | val NAVAJO_WHITE: ScageColor = new ScageColor("Navajo_White", 0xFF, 0xDE, 0xAD) 59 | val CORNSILK: ScageColor = new ScageColor("Cornsilk", 0xFF, 0xF8, 0xDC) 60 | val IVORY: ScageColor = new ScageColor("Ivory", 0xFF, 0xFF, 0xF0) 61 | val LEMON_CHIFFON: ScageColor = new ScageColor("Lemon_Chiffon", 0xFF, 0xFA, 0xCD) 62 | val SEASHELL: ScageColor = new ScageColor("Seashell", 0xFF, 0xF5, 0xEE) 63 | val HONEYDEW: ScageColor = new ScageColor("Honeydew", 0xF0, 0xFF, 0xF0) 64 | val AZURE: ScageColor = new ScageColor("Azure", 0xF0, 0xFF, 0xFF) 65 | val LAVENDER: ScageColor = new ScageColor("Lavender", 0xE6, 0xE6, 0xFA) 66 | val LAVENDER_BLUSH: ScageColor = new ScageColor("Lavender_Blush", 0xFF, 0xF0, 0xF5) 67 | val MISTY_ROSE: ScageColor = new ScageColor("Misty_Rose", 0xFF, 0xE4, 0xE1) 68 | val DIM_GRAY: ScageColor = new ScageColor("Dim_Gray", 0x69, 0x69, 0x69) 69 | val SLATE_GRAY: ScageColor = new ScageColor("Slate_Gray", 0x70, 0x80, 0x90) 70 | val LIGHT_SLATE_GRAY: ScageColor = new ScageColor("Light_Slate_Gray", 0x77, 0x88, 0x99) 71 | val LIGHT_GRAY: ScageColor = new ScageColor("Light_Gray", 0xC0, 0xC0, 0xC0) 72 | val MEDIUM_GRAY: ScageColor = new ScageColor("Medium_Gray", 0xA0, 0xA0, 0xA4) 73 | val DARK_GRAY: ScageColor = new ScageColor("Dark_Gray", 0.3f, 0.3f, 0.3f) 74 | val MIDNIGHT_BLUE: ScageColor = new ScageColor("Midnight_Blue", 0x19, 0x19, 0x70) 75 | val NAVY: ScageColor = new ScageColor("Navy", 0x00, 0x00, 0x80) 76 | val SLATE_BLUE: ScageColor = new ScageColor("Slate_Blue", 0x6A, 0x5A, 0xCD) 77 | val LIGHT_SLATE_BLUE: ScageColor = new ScageColor("Light_Slate_Blue", 0x84, 0x70, 0xFF) 78 | val ROYAL_BLUE: ScageColor = new ScageColor("Royal_Blue", 0x41, 0x69, 0xE1) 79 | val SKY_BLUE: ScageColor = new ScageColor("Sky_Blue", 0x87, 0xCE, 0xEB) 80 | val LIGHT_SKY_BLUE: ScageColor = new ScageColor("Light_Sky_Blue", 0x87, 0xCE, 0xFA) 81 | val STEEL_BLUE: ScageColor = new ScageColor("Steel_Blue", 0x46, 0x82, 0xB4) 82 | val LIGHT_STEEL_BLUE: ScageColor = new ScageColor("Light_Steel_Blue", 0xB0, 0xC4, 0xDE) 83 | val LIGHT_BLUE: ScageColor = new ScageColor("Light_Blue", 0xA6, 0xCA, 0xF0) 84 | val POWDER_BLUE: ScageColor = new ScageColor("Powder_Blue", 0xB0, 0xE0, 0xE6) 85 | val PALE_TURQUOISE: ScageColor = new ScageColor("Pale_Turquoise", 0xAF, 0xEE, 0xEE) 86 | val TURQUOISE: ScageColor = new ScageColor("Turquoise", 0x40, 0xE0, 0xD0) 87 | val LIGHT_CYAN: ScageColor = new ScageColor("Light_Cyan", 0xE0, 0xFF, 0xFF) 88 | val DARK_CYAN: ScageColor = new ScageColor("Dark_Cyan", 0x00, 0x80, 0x80) 89 | val CADET_BLUE: ScageColor = new ScageColor("Cadet_Blue", 0x5F, 0x9E, 0xA0) 90 | val AQUAMARINE: ScageColor = new ScageColor("Aquamarine", 0x7F, 0xFF, 0xD4) 91 | val SEAGREEN: ScageColor = new ScageColor("Seagreen", 0x54, 0xFF, 0x9F) 92 | val LIGHT_SEAGREEN: ScageColor = new ScageColor("Light_Seagreen", 0x20, 0xB2, 0xAA) 93 | val PALE_GREEN: ScageColor = new ScageColor("PaleGreen", 0x98, 0xFB, 0x98) 94 | val SPRING_GREEN: ScageColor = new ScageColor("Spring_Green", 0x00, 0xFF, 0x7F) 95 | val LAWN_GREEN: ScageColor = new ScageColor("Lawn_Green", 0x7C, 0xFC, 0x00) 96 | val MEDIUM_GREEN: ScageColor = new ScageColor("Medium_Green", 0xC0, 0xDC, 0xC0) 97 | val DARK_GREEN: ScageColor = new ScageColor("Dark_Green", 0x00, 0x80, 0x00) 98 | val CHARTREUSE: ScageColor = new ScageColor("Chartreuse", 0x7F, 0xFF, 0x00) 99 | val GREEN_YELLOW: ScageColor = new ScageColor("Green_Yellow", 0xAD, 0xFF, 0x2F) 100 | val LIME_GREEN: ScageColor = new ScageColor("Lime_Green", 0x32, 0xCD, 0x32) 101 | val YELLOW_GREEN: ScageColor = new ScageColor("Yellow_Green", 0x9A, 0xCD, 0x32) 102 | val FOREST_GREEN: ScageColor = new ScageColor("Forest_Green", 0x22, 0x8B, 0x22) 103 | val HAKI: ScageColor = new ScageColor("Haki", 0xF0, 0xE6, 0x8C) 104 | val PALE_GOLDENROD: ScageColor = new ScageColor("Pale_Goldenrod", 0xEE, 0xE8, 0xAA) 105 | val LIGHT_GOLDENROD_YELLOW: ScageColor = new ScageColor("Light_Goldenrod_Yellow", 0xFA, 0xFA, 0xD2) 106 | val LIGHT_YELLOW: ScageColor = new ScageColor("Light_Yellow", 0xFF, 0xFF, 0xE0) 107 | val DARK_YELLOW: ScageColor = new ScageColor("Dark_Yellow", 0x80, 0x80, 0x00) 108 | val GOLD: ScageColor = new ScageColor("Gold", 0xFF, 0xD7, 0x00) 109 | val LIGHT_GOLDENROD: ScageColor = new ScageColor("Light_Goldenrod", 0xFF, 0xEC, 0x8B) 110 | val GOLDEN_ROD: ScageColor = new ScageColor("Golden_Rod", 0xDA, 0xA5, 0x20) 111 | val BURLY_WOOD: ScageColor = new ScageColor("Burly_Wood", 0xDE, 0xB8, 0x87) 112 | val ROSY_BROWN: ScageColor = new ScageColor("Rosy_Brown", 0xBC, 0x8F, 0x8F) 113 | val SADDLE_BROWN: ScageColor = new ScageColor("Saddle_Brown", 0x8B, 0x45, 0x13) 114 | val SIENNA: ScageColor = new ScageColor("Sienna", 0xA0, 0x52, 0x2D) 115 | val BEIGE: ScageColor = new ScageColor("Beige", 0xF5, 0xF5, 0xDC) 116 | val WHEAT: ScageColor = new ScageColor("Wheat", 0xF5, 0xDE, 0xB3) 117 | val TAN: ScageColor = new ScageColor("Tan", 0xD2, 0xB4, 0x8C) 118 | val CHOCOLATE: ScageColor = new ScageColor("Chocolate", 0xD2, 0x69, 0x1E) 119 | val FIREBRICK: ScageColor = new ScageColor("Firebrick", 0xB2, 0x22, 0x22) 120 | val BROWN: ScageColor = new ScageColor("Brown", 0xA5, 0x2A, 0x2A) 121 | val SALMON: ScageColor = new ScageColor("Salmon", 0xFA, 0x80, 0x72) 122 | val LIGHT_SALMON: ScageColor = new ScageColor("Light_Salmon", 0xFF, 0xA0, 0x7A) 123 | val ORANGE: ScageColor = new ScageColor("Orange", 0xFF, 0xA5, 0x00) 124 | val CORAL: ScageColor = new ScageColor("Coral", 0xFF, 0x7F, 0x50) 125 | val LIGHT_CORAL: ScageColor = new ScageColor("Light_Coral", 0xF0, 0x80, 0x80) 126 | val ORANGE_RED: ScageColor = new ScageColor("Orange_Red", 0xFF, 0x45, 0x00) 127 | val DARK_RED: ScageColor = new ScageColor("Dark_Red", 0x80, 0x00, 0x00) 128 | val HOT_PINK: ScageColor = new ScageColor("Hot_Pink", 0xFF, 0x69, 0xB4) 129 | val PINK: ScageColor = new ScageColor("Pink", 0xFF, 0xC0, 0xCB) 130 | val LIGHT_PINK: ScageColor = new ScageColor("Light_Pink", 0xFF, 0xB6, 0xC1) 131 | val PALE_VIOLET_RED: ScageColor = new ScageColor("Pale_Violet_Red", 0xDB, 0x70, 0x93) 132 | val MAROON: ScageColor = new ScageColor("Maroon", 0xB0, 0x30, 0x60) 133 | val VIOLET_RED: ScageColor = new ScageColor("Violet_Red", 0xD0, 0x20, 0x90) 134 | val MAGENTA: ScageColor = new ScageColor("Magenta", 0xFF, 0x00, 0xFF) 135 | val DARK_MAGENTA: ScageColor = new ScageColor("Dark_Magenta", 0x80, 0x00, 0x80) 136 | val VIOLET: ScageColor = new ScageColor("Violet", 0xEE, 0x82, 0xEE) 137 | val PLUM: ScageColor = new ScageColor("Plum", 0xDD, 0xA0, 0xDD) 138 | val ORCHID: ScageColor = new ScageColor("Orchid", 0xDA, 0x70, 0xD6) 139 | val BLUE_VIOLET: ScageColor = new ScageColor("Blue_Violet", 0x8A, 0x2B, 0xE2) 140 | val PURPLE: ScageColor = new ScageColor("Purple", 0xA0, 0x20, 0xF0) 141 | 142 | private val colors = new mutable.HashMap[String, ScageColor]() 143 | this.getClass.getDeclaredFields.foreach(field => { 144 | field.setAccessible(true) 145 | val color = try{field.get(ScageColor).asInstanceOf[ScageColor]} 146 | catch { 147 | case ex:Exception => 148 | //log.error("failed to create color with name "+field.getName+": "+ex.getLocalizedMessage) 149 | DEFAULT_COLOR 150 | } 151 | colors += (field.getName.toUpperCase -> color) 152 | field.setAccessible(false) 153 | }) 154 | def fromString(color_str:String) = colors.get(color_str.trim().toUpperCase) 155 | def fromStringOrDefault(color_str:String, default:ScageColor = DEFAULT_COLOR) = 156 | colors.get(color_str.trim().toUpperCase) match { 157 | case Some(c:ScageColor) => c 158 | case None => default 159 | } 160 | 161 | def randomColor = new ScageColor(math.random.toFloat, math.random.toFloat, math.random.toFloat) 162 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/ScageMessageD.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages 2 | 3 | import com.github.dunnololda.scage.handlers.RendererLibD.currentColor 4 | import com.github.dunnololda.cli.AppProperties._ 5 | import com.github.dunnololda.scage.support.messages.unicode.UnicodeFont 6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 7 | import com.github.dunnololda.scage.support.{DVec, ScageColor} 8 | import com.github.dunnololda.scage.support.ScageColor._ 9 | import org.lwjgl.opengl.GL11 10 | 11 | trait ScageMessageTraitD { 12 | def max_font_size:Double 13 | 14 | // cannot replace it with method with default arguments as its way more inconvinient for a client apps, 15 | // also I have an error: two overloaded methods define default arguments (x:Double, y:Double and coord:DVec) 16 | def print(message:Any, x:Double, y:Double, size:Double, color:ScageColor, align:String) 17 | 18 | def print(message:Any, x:Double, y:Double, size:Double, color:ScageColor) {print(message, x, y, size, color, "default")} 19 | def print(message:Any, x:Double, y:Double, size:Double, align:String) {print(message, x, y, size, DEFAULT_COLOR, align)} 20 | def print(message:Any, x:Double, y:Double, color:ScageColor, align:String) {print(message, x, y, max_font_size, color, align)} 21 | def print(message:Any, x:Double, y:Double, align:String) {print(message, x, y, max_font_size, DEFAULT_COLOR, align)} 22 | def print(message:Any, x:Double, y:Double, size:Double) {print(message, x, y, size, DEFAULT_COLOR, "default")} 23 | def print(message:Any, x:Double, y:Double, color:ScageColor) {print(message, x, y, max_font_size, color, "default")} 24 | def print(message:Any, x:Double, y:Double) {print(message, x, y, max_font_size, currentColor, "default")} 25 | 26 | def print(message:Any, coord:DVec, size:Double, color:ScageColor, align:String) {print(message, coord.x, coord.y, size, color, align)} 27 | def print(message:Any, coord:DVec, size:Double, color:ScageColor) {print(message, coord.x, coord.y, size, color, "default")} 28 | def print(message:Any, coord:DVec, size:Double, align:String) {print(message, coord.x, coord.y, size, DEFAULT_COLOR, align)} 29 | def print(message:Any, coord:DVec, color:ScageColor, align:String) {print(message, coord.x, coord.y, max_font_size, color, align)} 30 | def print(message:Any, coord:DVec, align:String) {print(message, coord.x, coord.y, max_font_size, DEFAULT_COLOR, align)} 31 | def print(message:Any, coord:DVec, size:Double) {print(message, coord.x, coord.y, size, DEFAULT_COLOR, "default")} 32 | def print(message:Any, coord:DVec, color:ScageColor) {print(message, coord.x, coord.y, max_font_size, color, "default")} 33 | def print(message:Any, coord:DVec) {print(message, coord.x, coord.y, max_font_size, DEFAULT_COLOR, "default")} 34 | 35 | def messageBounds(message:Any, size:Double):DVec 36 | def areaForMessage(message:Any, coord:DVec, size:Double, align:String):Seq[DVec] 37 | 38 | def printCentered(message:Any, x:Double, y:Double, size:Double, color:ScageColor) { 39 | print(message, x, y, size, color, align = "center") 40 | } 41 | def printCentered(message:Any, x:Double, y:Double, size:Double) {printCentered(message:Any, x, y, size, DEFAULT_COLOR)} 42 | def printCentered(message:Any, x:Double, y:Double, color:ScageColor) {printCentered(message, x, y, max_font_size, color)} 43 | def printCentered(message:Any, x:Double, y:Double) {printCentered(message, x, y, max_font_size, currentColor)} 44 | def printCentered(message:Any, coord:DVec, size:Double, color:ScageColor) {printCentered(message, coord.x, coord.y, size, color)} 45 | def printCentered(message:Any, coord:DVec, color:ScageColor) {printCentered(message, coord, max_font_size, color)} 46 | def printCentered(message:Any, coord:DVec, size:Double) {printCentered(message, coord, size, DEFAULT_COLOR)} 47 | def printCentered(message:Any, coord:DVec) {printCentered(message, coord, max_font_size, DEFAULT_COLOR)} 48 | 49 | def printStrings(messages:TraversableOnce[Any], x:Double, y:Double, x_interval:Double = 0, y_interval:Double = -20, color:ScageColor = DEFAULT_COLOR) { 50 | var x_pos = x 51 | var y_pos = y 52 | for(message <- messages) { 53 | print(message, x_pos, y_pos, color) 54 | x_pos += x_interval 55 | y_pos += y_interval 56 | } 57 | } 58 | 59 | def printInterface(messages:TraversableOnce[MessageData], parameters:Any*) { 60 | for(MessageData(message, message_x, message_y, message_color) <- messages) { 61 | print(message, message_x, message_y, color = message_color) 62 | } 63 | } 64 | } 65 | 66 | class ScageMessageD( 67 | val fonts_base:String = property("fonts.base", "resources/fonts/"), 68 | val font_file:String = property("font.file", "DroidSans.ttf"), 69 | val max_font_size:Double = property("font.max_size", 18), 70 | val glyph_from:Int = property("font.glyph.from", 1024), 71 | val glyph_to:Int = property("font.glyph.to", 1279), 72 | val glyph_symbols:String = property("font.glyph.symbols", "") 73 | ) extends ScageMessageTraitD { 74 | private val log = MySimpleLogger(this.getClass.getName) 75 | 76 | private var _font:UnicodeFont = _ 77 | private def font = { 78 | if (_font == null) { 79 | reloadFont() 80 | } 81 | _font 82 | } 83 | 84 | def reloadFont() { 85 | _font = try { 86 | log.info("loading font "+fonts_base+font_file+"...") 87 | new UnicodeFont(fonts_base+font_file, max_font_size.toFloat, glyph_from, glyph_to, glyph_symbols) 88 | } catch { 89 | case e:Exception => 90 | log.error("failed to create font: "+e.getLocalizedMessage) 91 | log.error("please provide the path to some unicode ttf font") 92 | System.exit(1) 93 | null 94 | } 95 | } 96 | 97 | // DEFAULT_COLOR support because client programs CAN pass DEFAULT_COLOR 98 | // align is one of those: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right, default 99 | def print(message:Any, x:Double, y:Double, size:Double, color:ScageColor, align:String) { 100 | val print_color = if(color != DEFAULT_COLOR) color.toSlickColor else currentColor.toSlickColor 101 | val bounds = messageBounds(message, size) 102 | val num_lines = message.toString.filter(_ == '\n').length + 1 103 | val line_height = bounds.y/num_lines 104 | val (new_x, new_y) = align match { 105 | case "top-left" => 106 | val x_offset = x 107 | val y_offset = y - line_height 108 | (x_offset, y_offset) 109 | case "top-center" => 110 | val x_offset = x - bounds.ix/2 111 | val y_offset = y - line_height 112 | (x_offset, y_offset) 113 | case "top-right" => 114 | val x_offset = x - bounds.ix 115 | val y_offset = y - line_height 116 | (x_offset, y_offset) 117 | case "center-left" => 118 | val x_offset = x 119 | val y_offset = y + (bounds.y/2 - line_height) 120 | (x_offset, y_offset) 121 | case "center" => 122 | val x_offset = x - bounds.ix/2 123 | val y_offset = y - bounds.iy/2 + (bounds.y - line_height) 124 | (x_offset, y_offset) 125 | case "center-right" => 126 | val x_offset = x - bounds.ix 127 | val y_offset = y - bounds.iy/2 + (bounds.y - line_height) 128 | (x_offset, y_offset) 129 | case "bottom-left" => 130 | val x_offset = x 131 | val y_offset = y + (bounds.y - line_height) 132 | (x_offset, y_offset) 133 | case "bottom-center" => 134 | val x_offset = x - bounds.ix/2 135 | val y_offset = y + (bounds.y - line_height) 136 | (x_offset, y_offset) 137 | case "bottom-right" => 138 | val x_offset = x - bounds.ix 139 | val y_offset = y + (bounds.y - line_height) 140 | (x_offset, y_offset) 141 | case "default" => (x, y) 142 | case a => 143 | log.warn("unknow text align: "+a) 144 | (x, y) 145 | } 146 | 147 | GL11.glPushMatrix() 148 | font.drawString(new_x.toInt, new_y.toInt, size.toFloat, message.toString, print_color) 149 | GL11.glPopMatrix() 150 | } 151 | 152 | def messageBounds(message:Any, size:Double = max_font_size):DVec = { 153 | val msg_str = new ColoredString(message.toString, DEFAULT_COLOR).text 154 | DVec(font.getWidth(msg_str), font.getHeight(msg_str))*(size/max_font_size) 155 | } 156 | 157 | // align is one of those: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right, default 158 | def areaForMessage(message:Any, coord:DVec, size:Double = max_font_size, align:String = "center"):Seq[DVec] = { 159 | val DVec(w, h) = messageBounds(message, size) 160 | align match { 161 | case "top-left" => 162 | List(coord, coord + DVec(w, 0), coord + DVec(w, -h), coord + DVec(0, -h)) 163 | case "top-center" => 164 | List(coord + DVec(-w/2, 0), coord + DVec(w/2, 0), coord + DVec(w/2, -h), coord + DVec(-w/2, -h)) 165 | case "top-right" => 166 | List(coord + DVec(-w, 0), coord, coord + DVec(0, -h), coord + DVec(-w, -h)) 167 | case "center-left" => 168 | List(coord + DVec(0, h/2), coord + DVec(w, h/2), coord + DVec(w, -h/2), coord + DVec(0, -h/2)) 169 | case "center" => 170 | List(coord + DVec(-w/2, h/2), coord + DVec(w/2, h/2), coord + DVec(w/2, -h/2), coord + DVec(-w/2, -h/2)) 171 | case "center-right" => 172 | List(coord + DVec(-w, h/2), coord + DVec(0, h/2), coord + DVec(0, -h/2), coord + DVec(-w, -h/2)) 173 | case "bottom-left" => 174 | List(coord + DVec(0, h), coord + DVec(w, h), coord + DVec(w, 0), coord) 175 | case "bottom-center" => 176 | List(coord + DVec(-w/2, h), coord + DVec(w/2, h), coord + DVec(w/2, 0), coord + DVec(-w/2, 0)) 177 | case "bottom-right" => 178 | List(coord + DVec(-w, h), coord + DVec(0, h), coord, coord + DVec(-w, 0)) 179 | case "default" => 180 | val num_lines = message.toString.filter(_ == '\n').length + 1 181 | val lh = h/num_lines 182 | List(coord + DVec(0, h/2-lh), coord + DVec(w, h/2-lh), coord + DVec(w, -h/2-lh), coord + DVec(0, -h/2-lh)) 183 | case a => 184 | log.warn("unknown text align: "+a) 185 | List(coord + DVec(-w/2, h/2), coord + DVec(w/2, h/2), coord + DVec(w/2, -h/2), coord + DVec(-w/2, -h/2)) 186 | } 187 | } 188 | } 189 | 190 | object ScageMessageD extends ScageMessageD ( 191 | fonts_base = property("fonts.base", "resources/fonts/"), 192 | font_file = property("font.file", "DroidSans.ttf"), 193 | max_font_size = property("font.max_size", 18), 194 | glyph_from = property("font.glyph.from", 1024), 195 | glyph_to = property("font.glyph.to", 1279), 196 | glyph_symbols = property("font.glyph.symbols", "") 197 | ) -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/ScageXML.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages 2 | 3 | import com.github.dunnololda.cli.AppProperties._ 4 | import com.github.dunnololda.cli.FormulaParser 5 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 6 | import com.github.dunnololda.scage.support.ScageColor 7 | import com.github.dunnololda.scage.support.ScageColor._ 8 | import org.newdawn.slick.util.ResourceLoader 9 | 10 | import scala.collection.mutable 11 | import scala.xml.XML 12 | 13 | case class InterfaceData(interface_id:String, x:Int = -1, y:Int = -1, xinterval:Int = 0, yinterval:Int = 0, rows:Array[RowData], color:ScageColor = DEFAULT_COLOR) 14 | case class RowData(message_id:String, x:Int = -1, y:Int = -1, placeholders_before:Int = 0, placeholders_in_row:Int = 0, color:ScageColor = DEFAULT_COLOR) 15 | case class MessageData(message:String, x:Int = -1, y:Int = -1, color:ScageColor = DEFAULT_COLOR) 16 | 17 | trait ScageXMLTrait { 18 | def lang:String 19 | def lang_=(new_lang:String) 20 | 21 | def messagesBase:String 22 | def messagesBase_=(new_base:String) 23 | 24 | def interfacesBase:String 25 | def interfacesBase_=(new_base:String) 26 | 27 | def messagesFile:String 28 | def interfacesFile:String 29 | 30 | def xml(message_id:String, parameters:Any*):String 31 | def xmlOrDefault(message_id:String, parameters:Any*):String 32 | def xmlInterface(interface_id:String, parameters:Any*):Array[MessageData] 33 | def xmlInterfaceStrings(interface_id:String, parameters:Any*):Array[String] 34 | } 35 | 36 | class ScageXML(private var _lang:String = property("xml.lang", "en"), 37 | private var messages_base:String = property("xml.strings.base", "resources/strings/" +appName+"_strings"), 38 | private var interfaces_base:String = property("xml.interfaces.base", "resources/interfaces/"+appName+"_interfaces") 39 | ) extends ScageXMLTrait { 40 | private val log = MySimpleLogger(this.getClass.getName) 41 | 42 | def messagesBase = messages_base 43 | def messagesBase_=(new_base:String) {messages_base = new_base} 44 | 45 | def interfacesBase = interfaces_base 46 | def interfacesBase_=(new_base:String) {interfaces_base = new_base} 47 | 48 | def lang = _lang 49 | def lang_=(new_lang:String) { 50 | _lang = new_lang 51 | messages_file = messages_base + "_" + _lang + ".xml" 52 | xml_messages = null 53 | 54 | interfaces_file = interfaces_base + "_" + _lang + ".xml" 55 | xml_interfaces = null 56 | } 57 | 58 | private var messages_file = messages_base + "_" + _lang + ".xml" 59 | def messagesFile = messages_file 60 | 61 | private var xml_messages:mutable.HashMap[String, String] = _ 62 | private def xmlMessages = xml_messages match { 63 | case null => 64 | xml_messages = try { 65 | log.debug("parsing xml messages from file "+messages_file) 66 | XML.load(ResourceLoader.getResourceAsStream(messages_file)) match { 67 | case {messages_list @ _*} => 68 | mutable.HashMap((for { 69 | message @ {_*} <- messages_list 70 | message_id = (message \ "@id").text 71 | if message_id != "" 72 | message_text = message.text.trim 73 | } yield { 74 | log.debug("added message "+message_id) 75 | (message_id, message_text) 76 | }):_*) 77 | case _ => mutable.HashMap[String, String]() // TODO: log messages 78 | } 79 | } catch { 80 | case ex:Exception => mutable.HashMap[String, String]() // TODO: log messages 81 | } 82 | xml_messages 83 | case _ => xml_messages 84 | } 85 | 86 | private def mergeMessage(xml_message:String, parameters:Any*) = { 87 | parameters.zipWithIndex.foldLeft(xml_message)((message, param) => { 88 | message.replaceAll("\\$"+param._2, param._1.toString) 89 | }) 90 | } 91 | 92 | def xml(message_id:String, parameters:Any*):String = { 93 | if(message_id == "") "" 94 | else { 95 | xmlMessages.get(message_id) match { 96 | case Some(message) => mergeMessage(message, parameters:_*) 97 | case None => 98 | log.warn("failed to find string with id "+message_id) 99 | xmlMessages += (message_id -> message_id) 100 | message_id 101 | } 102 | } 103 | } 104 | 105 | def xmlOrDefault(message_id:String, parameters:Any*):String = { 106 | xmlMessages.get(message_id) match { 107 | case Some(message) => mergeMessage(message, parameters.tail:_*) 108 | case None if parameters.size > 0 => 109 | log.info("default value for string id "+message_id+" is "+{ 110 | if("" == parameters.head) "empty string" else parameters.head 111 | }) 112 | xmlMessages += (message_id -> parameters.head.toString) 113 | mergeMessage(parameters.head.toString, parameters.tail:_*) 114 | case _ => 115 | log.warn("failed to find default message for the id "+message_id) 116 | xmlMessages += (message_id -> message_id) 117 | message_id 118 | } 119 | } 120 | 121 | private def placeholdersAmount(message:String) = { 122 | message.foldLeft(0)((sum, char) => sum + (if(char == '$') 1 else 0)) 123 | } 124 | 125 | private lazy val formula_parser = new FormulaParser( // maybe use formula_parser from ScageProperties 126 | constants = mutable.HashMap("window_width" -> property("screen.width", 800), 127 | "window_height" -> property("screen.height", 600)) 128 | ) 129 | 130 | private var interfaces_file = interfaces_base + "_" + _lang + ".xml" 131 | def interfacesFile = interfaces_file 132 | 133 | private var xml_interfaces:mutable.HashMap[String, InterfaceData] = null 134 | private def xmlInterfaces = xml_interfaces match { 135 | case null => 136 | xml_interfaces = try { 137 | log.debug("parsing xml interfaces from file "+interfaces_file) 138 | XML.load(ResourceLoader.getResourceAsStream(interfaces_file)) match { 139 | case {interfaces_list @ _*} => 140 | mutable.HashMap((for { 141 | interface @ {rows_list @ _*} <- interfaces_list 142 | interface_id = (interface \ "@id").text 143 | if interface_id != "" 144 | interface_x = try{formula_parser.calculate((interface \ "@x").text).toInt} catch {case ex:Exception => -1} 145 | interface_y = try{formula_parser.calculate((interface \ "@y").text).toInt} catch {case ex:Exception => -1} 146 | interface_xinterval = try{formula_parser.calculate((interface \ "@xinterval").text).toInt} catch {case ex:Exception => 0} 147 | interface_yinterval = try{formula_parser.calculate((interface \ "@yinterval").text).toInt} catch {case ex:Exception => 0} 148 | interface_color = fromStringOrDefault((interface \ "@color").text) 149 | } yield { 150 | var placeholders_before = 0 151 | var xpos = interface_x 152 | var ypos = interface_y 153 | var curcolor = interface_color 154 | val messages = (for { 155 | row @ {_*} <- rows_list 156 | message_id = (row \ "@id").text 157 | message = row.text 158 | message_x = try{formula_parser.calculate((row \ "@x").text).toInt} catch {case ex:Exception => -1} 159 | message_y = try{formula_parser.calculate((row \ "@y").text).toInt} catch {case ex:Exception => -1} 160 | placeholders_in_row = placeholdersAmount(message) 161 | message_color = fromStringOrDefault((row \ "@color").text) 162 | } yield { 163 | if(message != "") { 164 | xmlMessages += (message_id -> message) 165 | log.debug("added message "+message_id) 166 | } 167 | val to_yield_x = if(message_x != -1) message_x else xpos // priority to coords and color in tag row 168 | val to_yield_y = if(message_y != -1) message_y else ypos 169 | if(message_color != DEFAULT_COLOR) curcolor = message_color 170 | val to_yield = RowData(message_id, to_yield_x, to_yield_y, placeholders_before, placeholders_in_row, curcolor) 171 | placeholders_before += placeholders_in_row 172 | if(message_x == -1) xpos += interface_xinterval 173 | if(message_y == -1) ypos += interface_yinterval 174 | to_yield 175 | }).toArray 176 | log.debug("added interfaces "+interface_id) 177 | (interface_id, InterfaceData(interface_id, interface_x, interface_y, interface_xinterval, interface_yinterval, messages, interface_color)) 178 | }):_*) 179 | case _ => mutable.HashMap[String, InterfaceData]() // TODO: log messages 180 | } 181 | } catch { 182 | case ex:Exception => mutable.HashMap[String, InterfaceData]() // TODO: log messages 183 | } 184 | xml_interfaces 185 | case _ => xml_interfaces 186 | } 187 | 188 | def xmlInterface(interface_id:String, parameters:Any*):Array[MessageData] = { 189 | xmlInterfaces.get(interface_id) match { 190 | case Some(InterfaceData(_, interface_x, interface_y, interface_xinterval, interface_yinterval, rows, interface_color)) => 191 | for { 192 | RowData(message_id, message_x, message_y, params_from, params_take, message_color) <- rows 193 | } yield { 194 | val to_yield = MessageData(xml(message_id, parameters.drop(params_from).take(params_take): _*), message_x, message_y, message_color) 195 | to_yield 196 | } 197 | case None => 198 | log.warn("failed to find interfaces with id "+interface_id) 199 | xmlInterfaces += (interface_id -> InterfaceData(interface_id, rows = Array(/*RowData(interface_id)*/))) 200 | xmlMessages += (interface_id -> interface_id) 201 | Array(MessageData(interface_id)) 202 | } 203 | } 204 | 205 | def xmlInterfaceStrings(interface_id:String, parameters:Any*):Array[String] = { 206 | xmlInterfaces.get(interface_id) match { 207 | case Some(InterfaceData(_, _, _, _, _, rows, _)) => 208 | (for { 209 | RowData(message_id, _, _, params_from, params_take, _) <- rows 210 | } yield xml(message_id, parameters.drop(params_from).take(params_take):_*)).toArray 211 | case None => 212 | log.warn("failed to find interfaces with id "+interface_id) 213 | xmlInterfaces += (interface_id -> InterfaceData(interface_id, rows = Array(RowData(interface_id)))) 214 | xmlMessages += (interface_id -> interface_id) 215 | Array(interface_id) 216 | } 217 | } 218 | } 219 | 220 | object ScageXML extends ScageXML( 221 | _lang = property("xml.lang", "en"), 222 | messages_base = property("xml.strings.base", "resources/strings/" +stringProperty("app.name").toLowerCase+"_strings"), 223 | interfaces_base = property("xml.interfaces.base", "resources/interfaces/"+stringProperty("app.name").toLowerCase+"_interfaces") 224 | ) -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/messages/ScageMessage.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.messages 2 | 3 | import com.github.dunnololda.scage.handlers.RendererLib.currentColor 4 | import com.github.dunnololda.cli.AppProperties._ 5 | import com.github.dunnololda.scage.support.messages.unicode.UnicodeFont 6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 7 | import com.github.dunnololda.scage.support.{Vec, ScageColor} 8 | import com.github.dunnololda.scage.support.ScageColor._ 9 | import org.lwjgl.opengl.GL11 10 | 11 | trait ScageMessageTrait { 12 | def max_font_size:Float 13 | 14 | // cannot replace it with method with default arguments as its way more inconvinient for a client apps, 15 | // also I have an error: two overloaded methods define default arguments (x:Float, y:Float and coord:Vec) 16 | def print(message:Any, x:Float, y:Float, size:Float, color:ScageColor, align:String) 17 | 18 | def print(message:Any, x:Float, y:Float, size:Float, color:ScageColor) {print(message, x, y, size, color, "default")} 19 | def print(message:Any, x:Float, y:Float, size:Float, align:String) {print(message, x, y, size, DEFAULT_COLOR, align)} 20 | def print(message:Any, x:Float, y:Float, color:ScageColor, align:String) {print(message, x, y, max_font_size, color, align)} 21 | def print(message:Any, x:Float, y:Float, align:String) {print(message, x, y, max_font_size, DEFAULT_COLOR, align)} 22 | def print(message:Any, x:Float, y:Float, size:Float) {print(message, x, y, size, DEFAULT_COLOR, "default")} 23 | def print(message:Any, x:Float, y:Float, color:ScageColor) {print(message, x, y, max_font_size, color, "default")} 24 | def print(message:Any, x:Float, y:Float) {print(message, x, y, max_font_size, currentColor, "default")} 25 | 26 | def print(message:Any, coord:Vec, size:Float, color:ScageColor, align:String) {print(message, coord.x, coord.y, size, color, align)} 27 | def print(message:Any, coord:Vec, size:Float, color:ScageColor) {print(message, coord.x, coord.y, size, color, "default")} 28 | def print(message:Any, coord:Vec, size:Float, align:String) {print(message, coord.x, coord.y, size, DEFAULT_COLOR, align)} 29 | def print(message:Any, coord:Vec, color:ScageColor, align:String) {print(message, coord.x, coord.y, max_font_size, color, align)} 30 | def print(message:Any, coord:Vec, align:String) {print(message, coord.x, coord.y, max_font_size, DEFAULT_COLOR, align)} 31 | def print(message:Any, coord:Vec, size:Float) {print(message, coord.x, coord.y, size, DEFAULT_COLOR, "default")} 32 | def print(message:Any, coord:Vec, color:ScageColor) {print(message, coord.x, coord.y, max_font_size, color, "default")} 33 | def print(message:Any, coord:Vec) {print(message, coord.x, coord.y, max_font_size, DEFAULT_COLOR, "default")} 34 | 35 | def messageBounds(message:Any, size:Float):Vec 36 | def areaForMessage(message:Any, coord:Vec, size:Float, align:String):Seq[Vec] 37 | 38 | def printCentered(message:Any, x:Float, y:Float, size:Float, color:ScageColor) { 39 | print(message, x, y, size, color, align = "center") 40 | } 41 | def printCentered(message:Any, x:Float, y:Float, size:Float) {printCentered(message:Any, x, y, size, DEFAULT_COLOR)} 42 | def printCentered(message:Any, x:Float, y:Float, color:ScageColor) {printCentered(message, x, y, max_font_size, color)} 43 | def printCentered(message:Any, x:Float, y:Float) {printCentered(message, x, y, max_font_size, currentColor)} 44 | def printCentered(message:Any, coord:Vec, size:Float, color:ScageColor) {printCentered(message, coord.x, coord.y, size, color)} 45 | def printCentered(message:Any, coord:Vec, color:ScageColor) {printCentered(message, coord, max_font_size, color)} 46 | def printCentered(message:Any, coord:Vec, size:Float) {printCentered(message, coord, size, DEFAULT_COLOR)} 47 | def printCentered(message:Any, coord:Vec) {printCentered(message, coord, max_font_size, DEFAULT_COLOR)} 48 | 49 | def printStrings(messages:TraversableOnce[Any], x:Float, y:Float, x_interval:Float = 0, y_interval:Float = -20, color:ScageColor = DEFAULT_COLOR) { 50 | var x_pos = x 51 | var y_pos = y 52 | for(message <- messages) { 53 | print(message, x_pos, y_pos, color) 54 | x_pos += x_interval 55 | y_pos += y_interval 56 | } 57 | } 58 | 59 | def printInterface(messages:TraversableOnce[MessageData], parameters:Any*) { 60 | for(MessageData(message, message_x, message_y, message_color) <- messages) { 61 | print(message, message_x, message_y, color = message_color) 62 | } 63 | } 64 | 65 | def addGlyphs(from:Int, to:Int): Unit 66 | def addGlyphs(text:String): Unit 67 | } 68 | 69 | class ScageMessage( 70 | val fonts_base:String = property("fonts.base", "resources/fonts/"), 71 | val font_file:String = property("font.file", "DroidSans.ttf"), 72 | val max_font_size:Float = property("font.max_size", 18), 73 | val glyph_from:Int = property("font.glyph.from", 1024), 74 | val glyph_to:Int = property("font.glyph.to", 1279), 75 | val glyph_symbols:String = property("font.glyph.symbols", "") 76 | ) extends ScageMessageTrait { 77 | private val log = MySimpleLogger(this.getClass.getName) 78 | 79 | private var _font:UnicodeFont = _ 80 | private def font = { 81 | if (_font == null) { 82 | reloadFont() 83 | } 84 | _font 85 | } 86 | 87 | def addGlyphs(from:Int, to:Int) { 88 | font.addGlyphs(from ,to) 89 | font.loadGlyphs() 90 | } 91 | 92 | def addGlyphs(text:String) { 93 | font.addGlyphs(text) 94 | font.loadGlyphs() 95 | } 96 | 97 | def reloadFont() { 98 | _font = try { 99 | log.info("loading font "+fonts_base+font_file+"...") 100 | new UnicodeFont(fonts_base+font_file, max_font_size, glyph_from, glyph_to, glyph_symbols) 101 | } catch { 102 | case e:Exception => 103 | log.error("failed to create font: "+e.getLocalizedMessage) 104 | log.error("please provide the path to some unicode ttf font") 105 | System.exit(1) 106 | null 107 | } 108 | } 109 | 110 | // DEFAULT_COLOR support because client programs CAN pass DEFAULT_COLOR 111 | // align is one of those: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right, default 112 | def print(message:Any, x:Float, y:Float, size:Float, color:ScageColor, align:String) { 113 | val print_color = if(color != DEFAULT_COLOR) color.toSlickColor else currentColor.toSlickColor 114 | val bounds = messageBounds(message, size) 115 | val num_lines = message.toString.filter(_ == '\n').length + 1 116 | val line_height = bounds.y/num_lines 117 | val (new_x, new_y) = align match { 118 | case "top-left" => 119 | val x_offset = x 120 | val y_offset = y - line_height 121 | (x_offset, y_offset) 122 | case "top-center" => 123 | val x_offset = x - bounds.x/2 124 | val y_offset = y - line_height 125 | (x_offset, y_offset) 126 | case "top-right" => 127 | val x_offset = x - bounds.x 128 | val y_offset = y - line_height 129 | (x_offset, y_offset) 130 | case "center-left" => 131 | val x_offset = x 132 | val y_offset = y + (bounds.y/2 - line_height) 133 | (x_offset, y_offset) 134 | case "center" => 135 | val x_offset = x - bounds.x/2 136 | val y_offset = y - bounds.y/2 + (bounds.y - line_height) 137 | (x_offset, y_offset) 138 | case "center-right" => 139 | val x_offset = x - bounds.x 140 | val y_offset = y - bounds.y/2 + (bounds.y - line_height) 141 | (x_offset, y_offset) 142 | case "bottom-left" => 143 | val x_offset = x 144 | val y_offset = y + (bounds.y - line_height) 145 | (x_offset, y_offset) 146 | case "bottom-center" => 147 | val x_offset = x - bounds.x/2 148 | val y_offset = y + (bounds.y - line_height) 149 | (x_offset, y_offset) 150 | case "bottom-right" => 151 | val x_offset = x - bounds.x 152 | val y_offset = y + (bounds.y - line_height) 153 | (x_offset, y_offset) 154 | case "default" => (x, y) 155 | case a => 156 | log.warn("unknow text align: "+a) 157 | (x, y) 158 | } 159 | 160 | GL11.glPushMatrix() 161 | font.drawString(new_x.toInt, new_y.toInt, size, message.toString, print_color) 162 | GL11.glPopMatrix() 163 | } 164 | 165 | def messageBounds(message:Any, size:Float = max_font_size) = { 166 | val msg_str = new ColoredString(message.toString, DEFAULT_COLOR).text 167 | Vec(font.getWidth(msg_str), font.getHeight(msg_str))*(size/max_font_size) 168 | } 169 | 170 | // align is one of those: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right, default 171 | def areaForMessage(message:Any, coord:Vec, size:Float = max_font_size, align:String = "center"):Seq[Vec] = { 172 | val Vec(w, h) = messageBounds(message, size) 173 | align match { 174 | case "top-left" => 175 | List(coord, coord + Vec(w, 0), coord + Vec(w, -h), coord + Vec(0, -h)) 176 | case "top-center" => 177 | List(coord + Vec(-w/2, 0), coord + Vec(w/2, 0), coord + Vec(w/2, -h), coord + Vec(-w/2, -h)) 178 | case "top-right" => 179 | List(coord + Vec(-w, 0), coord, coord + Vec(0, -h), coord + Vec(-w, -h)) 180 | case "center-left" => 181 | List(coord + Vec(0, h/2), coord + Vec(w, h/2), coord + Vec(w, -h/2), coord + Vec(0, -h/2)) 182 | case "center" => 183 | List(coord + Vec(-w/2, h/2), coord + Vec(w/2, h/2), coord + Vec(w/2, -h/2), coord + Vec(-w/2, -h/2)) 184 | case "center-right" => 185 | List(coord + Vec(-w, h/2), coord + Vec(0, h/2), coord + Vec(0, -h/2), coord + Vec(-w, -h/2)) 186 | case "bottom-left" => 187 | List(coord + Vec(0, h), coord + Vec(w, h), coord + Vec(w, 0), coord) 188 | case "bottom-center" => 189 | List(coord + Vec(-w/2, h), coord + Vec(w/2, h), coord + Vec(w/2, 0), coord + Vec(-w/2, 0)) 190 | case "bottom-right" => 191 | List(coord + Vec(-w, h), coord + Vec(0, h), coord, coord + Vec(-w, 0)) 192 | case "default" => 193 | val num_lines = message.toString.filter(_ == '\n').length + 1 194 | val lh = h/num_lines 195 | List(coord + Vec(0, h/2-lh), coord + Vec(w, h/2-lh), coord + Vec(w, -h/2-lh), coord + Vec(0, -h/2-lh)) 196 | case a => 197 | log.warn("unknown text align: "+a) 198 | List(coord + Vec(-w/2, h/2), coord + Vec(w/2, h/2), coord + Vec(w/2, -h/2), coord + Vec(-w/2, -h/2)) 199 | } 200 | } 201 | } 202 | 203 | object ScageMessage extends ScageMessage ( 204 | fonts_base = property("fonts.base", "resources/fonts/"), 205 | font_file = property("font.file", "DroidSans.ttf"), 206 | max_font_size = property("font.max_size", 18), 207 | glyph_from = property("font.glyph.from", 1024), 208 | glyph_to = property("font.glyph.to", 1279), 209 | glyph_symbols = property("font.glyph.symbols", "") 210 | ) -------------------------------------------------------------------------------- /src/test/scala/com/github/dunnololda/scage/test/ScageTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.test 2 | 3 | import javax.swing.JOptionPane 4 | 5 | import com.github.dunnololda.scage.ScageLib.{CoordTracer, DynaBall, MultiController, ScageColor, ScageMessage, ScagePhysics, StaticPolygon, Trace, Vec, appVersion, print, property, stopApp, xml, _} 6 | import junit.framework.Assert._ 7 | import junit.framework._ 8 | 9 | import scala.collection.mutable.ListBuffer 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.concurrent.Future 12 | import scala.language.reflectiveCalls 13 | 14 | object ScageTest { 15 | def suite: Test = { 16 | val suite = new TestSuite(classOf[ScageTest]) 17 | suite 18 | } 19 | 20 | def main(args : Array[String]) { 21 | junit.textui.TestRunner.run(suite) 22 | } 23 | } 24 | 25 | /** 26 | * Unit test for simple App. 27 | */ 28 | class ScageTest extends TestCase("app") { 29 | 30 | /** 31 | * Rigourous Tests :-) 32 | */ 33 | def testOK() { 34 | new ScreenApp("Hello World", 1024, 768) with MultiController { 35 | windowTitle += " - "+appVersion 36 | /*scage_log.info("starting main unit "+unit_name+"...") 37 | ScageProperties.properties = properties*/ 38 | 39 | def str2color(str:String):ScageColor = { 40 | str match { 41 | case s if s.equalsIgnoreCase("green")=> GREEN 42 | case _ => RED 43 | } 44 | } 45 | 46 | val rect_color = str2color(property("rect.color", "RED")) 47 | val tracer = CoordTracer() 48 | 49 | val trace = tracer.addTrace(Vec(windowWidth/2, windowHeight/2)) 50 | render(10) { 51 | drawTraceLocations(tracer, GREEN, 1) 52 | } 53 | 54 | val another_trace = tracer.addTrace(Vec(windowWidth/4, windowHeight/2)) 55 | 56 | def moveIfFreeLocation(trace:Trace, delta:Vec) { 57 | val new_location = trace.location + delta 58 | if(!tracer.hasCollisions(trace.id, new_location, 20)) // test collisions using tracer 59 | tracer.updateLocation(trace.id, new_location) 60 | } 61 | 62 | anykey(onKeyDown = println("any key pressed =)")) // test special method to obtain "press any key" event 63 | 64 | private var wanna_go_up = false 65 | key(KEY_W, onKeyDown = wanna_go_up = true, onKeyUp = wanna_go_up = false) 66 | private var wanna_go_left = false 67 | key(KEY_A, onKeyDown = wanna_go_left = true, onKeyUp = wanna_go_left = false) 68 | private var wanna_go_down = false 69 | key(KEY_S, onKeyDown = wanna_go_down = true, onKeyUp = wanna_go_down = false) 70 | private var wanna_go_right = false 71 | key(KEY_D, onKeyDown = wanna_go_right = true, onKeyUp = wanna_go_right = false) 72 | 73 | var dir = Vec.zero 74 | action { 75 | dir = Vec.zero 76 | if(wanna_go_up) dir += Vec(0, 1) 77 | if(wanna_go_down) dir += Vec(0, -1) 78 | if(wanna_go_right) dir += Vec(1, 0) 79 | if(wanna_go_left) dir += Vec(-1, 0) 80 | moveIfFreeLocation(trace, dir.n) 81 | } 82 | 83 | key(KEY_W, onKeyDown = println("also, W was pressed :3")) // test for multiple functions on one key 84 | 85 | private var input_text = "" 86 | key(KEY_F1, onKeyDown = Future { 87 | input_text = JOptionPane.showInputDialog("Input text here") 88 | }) 89 | keyIgnorePause(KEY_Q, onKeyDown = {if(keyPressed(KEY_LCONTROL)) stopApp()}) 90 | 91 | /*leftMouse(onBtnDown = { 92 | mouse_coord => tracer.updateLocation(trace, mouse_coord) 93 | })*/ 94 | 95 | val physics = new ScagePhysics 96 | val poly_physical = physics.addPhysical(new StaticPolygon(Vec(100, 300), Vec(150, 250), Vec(300, 300), Vec(300, 450), Vec(200, 400))) 97 | val poly_render = displayList { // test special method to save any opengl code as display list 98 | drawPolygon(poly_physical.points, CYAN) 99 | } 100 | 101 | val stars = displayList { // I like "starry sky" since high school =) 102 | for(i <- 1 to 100) { 103 | drawPoint(Vec(math.random.toFloat*windowWidth, math.random.toFloat*windowHeight), randomColor) 104 | } 105 | } 106 | 107 | private var target_point = trace.location 108 | mouseMotion { // test mouse motion event 109 | mouse_coord => 110 | target_point = (absCoord(mouse_coord) - trace.location).n * 20 111 | } 112 | private var x = 0.0f 113 | def period = { 114 | x += 0.01f 115 | if(x > 2*math.Pi) x = 0 116 | (125 * 0.25f* math.sin(x) + 1).toLong 117 | } 118 | actionDynamicPeriod(period) { // test actions with non-static period defined as function 119 | physics.step() 120 | } 121 | leftMouse(onBtnDown = { // test left mouse event 122 | mouse_coord => physics.addPhysical(new DynaBall(trace.location + target_point, 2) { 123 | val ball_trace = tracer.addTrace(trace.location + target_point) 124 | val action_id:Int = action { 125 | tracer.updateLocation(ball_trace.id, coord) 126 | coord = ball_trace.location 127 | /*if(!tracer.isCoordOnArea(coord)) { 128 | isActive = false 129 | delActionOperation(action_id) 130 | }*/ 131 | } 132 | 133 | velocity = target_point.n*10 134 | render { 135 | /*if(physics.containsPhysical(this)) */drawFilledCircle(coord, 2, YELLOW) 136 | } 137 | }) 138 | }) 139 | 140 | backgroundColor = fromStringOrDefault("BLACK", BLACK) // test method to obtain color by name 141 | val another_font = new ScageMessage(max_font_size = 15, font_file = "comic.ttf") // test using two different fonts in one app 142 | interface { 143 | another_font.print(xml("hello.world"), windowWidth/2, windowHeight/2+20, WHITE) 144 | } 145 | 146 | interfaceFromXml("scagetest.help", Array(trace.location, tracer.point(trace.location), fps, input_text)) 147 | 148 | private var touches:ListBuffer[(Vec, Long)] = ListBuffer() // test obtaining touching points for physical objects 149 | action { 150 | for { 151 | (point, _) <- poly_physical.touchingPoints 152 | if !touches.exists(_._1 == point) 153 | } touches += ((point, System.currentTimeMillis)) 154 | 155 | for { 156 | t <- touches 157 | if System.currentTimeMillis - t._2 > 5000 158 | } touches -= t 159 | } 160 | 161 | render { 162 | def pew(v:Vec) { 163 | drawFilledRect(Vec(30, 30)+v, 60, 20, rect_color) 164 | drawDisplayList(stars,v) 165 | drawFilledCircle(trace.location+v, 10, RED) 166 | drawLine(trace.location+v, trace.location+v + target_point+v) 167 | drawCircle(another_trace.location+v, 10, GREEN) 168 | drawDisplayList(poly_render, v) 169 | //physics.physicals.foreach(p => drawFilledCircle(p.coord+v, 2, YELLOW)) 170 | for((point, _) <- touches) drawFilledCircle(point+v, 3, RED) 171 | } 172 | pew(Vec.zero) 173 | if(globalScale > 1) { 174 | pew(Vec(0, windowHeight - 40)) 175 | pew(Vec(0, -windowHeight + 40)) 176 | pew(Vec(windowWidth - 80, 0)) 177 | pew(Vec(-windowWidth + 80, 0)) 178 | 179 | pew(Vec(windowWidth - 80, windowHeight - 40)) 180 | pew(Vec(windowWidth - 80, -windowHeight + 40)) 181 | pew(Vec(-windowWidth + 80, windowHeight - 40)) 182 | pew(Vec(-windowWidth + 80, -windowHeight + 40)) 183 | } 184 | } 185 | render(10) { 186 | def pew(v:Vec) {drawFilledRect(Vec(100, 30)+v, 60, 20, YELLOW)} 187 | pew(Vec.zero) 188 | if(globalScale > 1) { 189 | pew(Vec(0, windowHeight - 40)) 190 | pew(Vec(0, -windowHeight + 40)) 191 | pew(Vec(windowWidth - 80, 0)) 192 | pew(Vec(-windowWidth + 80, 0)) 193 | 194 | pew(Vec(windowWidth - 80, windowHeight - 40)) 195 | pew(Vec(windowWidth - 80, -windowHeight + 40)) 196 | pew(Vec(-windowWidth + 80, windowHeight - 40)) 197 | pew(Vec(-windowWidth + 80, -windowHeight + 40)) 198 | } 199 | } 200 | render(-10) { 201 | def pew(v:Vec) {drawLines(tracer.trace_grid.map(_ + v), DARK_GRAY)} 202 | pew(Vec.zero) 203 | if(globalScale > 1) { 204 | pew(Vec(0, windowHeight - 40)) 205 | pew(Vec(0, -windowHeight + 40)) 206 | pew(Vec(windowWidth - 80, 0)) 207 | pew(Vec(-windowWidth + 80, 0)) 208 | 209 | pew(Vec(windowWidth - 80, windowHeight - 40)) 210 | pew(Vec(windowWidth - 80, -windowHeight + 40)) 211 | pew(Vec(-windowWidth + 80, windowHeight - 40)) 212 | pew(Vec(-windowWidth + 80, -windowHeight + 40)) 213 | } 214 | } 215 | 216 | // scaling test 217 | mouseWheelUp(onWheelUp = m => globalScale += 1) 218 | mouseWheelDown(onWheelDown = m => if(globalScale > 1) globalScale -= 1) 219 | center = if(globalScale > 1) trace.location else windowCenter 220 | 221 | // test network features: server and client in one app. Client sends 2d vectors to server and server sends back normalized vector 222 | /*NetServer.startServer( 223 | onNewConnection = { 224 | client => client.send(State(("hello" -> "send me vec and I send you back its n!"))) 225 | (true, "") 226 | }, 227 | onClientDataReceived = { 228 | (client, received_data) => received_data.neededKeys { 229 | case ("vec", vec:Vec) => { 230 | client.send(State(("n" -> vec.n))) 231 | } 232 | } 233 | }, 234 | onClientQuestion = {(client, question) => 235 | val answer = State() 236 | question.neededKeys { 237 | case ("Are u ready?", true) => answer.add("yes") 238 | } 239 | answer 240 | } 241 | ) 242 | 243 | action { 244 | if(NetServer.connectionPort != 0) { 245 | NetClient.startClient( 246 | server_url = "localhost", 247 | port = NetServer.connectionPort, 248 | onServerDataReceived = { 249 | received_data => received_data.neededKeys { 250 | case ("hello", hello_msg) => 251 | val random_vec = Vec((math.random*100).toInt, (math.random*100).toInt) 252 | NetClient.send(State(("vec" -> random_vec))) 253 | case ("n", n:Vec) => 254 | println("received n: "+n) 255 | println("waiting 5 sec...") 256 | Thread.sleep(5000) 257 | println("asking if the server is ready to receive another vec") 258 | val answer = NetClient.askServer(State("Are u ready?")) 259 | if(answer.contains("yes")) { 260 | val random_vec = Vec((math.random*100).toInt, (math.random*100).toInt) 261 | NetClient.send(State(("vec" -> random_vec))) 262 | } 263 | } 264 | } 265 | ) 266 | deleteSelf() 267 | } 268 | } 269 | 270 | dispose { 271 | NetServer.stopServer() 272 | NetClient.stopClient() 273 | }*/ 274 | 275 | render { 276 | openglMove(windowCenter) 277 | openglRotate((msecsFromInit % 3600).toFloat/10) 278 | drawFilledPolygon(Array(Vec(-20, -5), Vec(20, -5), Vec(0, 20)), GREEN) 279 | } 280 | 281 | // window button test 282 | val window_button_area = List(windowCenter + windowCenter/2 + Vec(-40, -40), 283 | windowCenter + windowCenter/2 + Vec(40, -40), 284 | windowCenter + windowCenter/2 + Vec(0, 40)) 285 | val window_button = leftMouseOnArea(area = window_button_area, 286 | onBtnDown = m => {println("window button pressed")}, 287 | onBtnUp = m => {println("window button released")}) 288 | render { 289 | val window_button_pressed = leftMousePressed && mouseOnArea(window_button_area) 290 | currentColor = if(window_button_pressed) RED else WHITE 291 | drawPolygon(window_button_area) 292 | if(!window_button_pressed) print("Press Me", windowCenter + windowCenter/2, align = "center") 293 | else print("Release Me", windowCenter + windowCenter/2, align = "center") 294 | } 295 | 296 | }.main(Array[String]()) 297 | assertTrue(true) 298 | } 299 | } 300 | 301 | /*test*/ 302 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/support/tracer3/ScageTracer.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.support.tracer3 2 | 3 | import com.github.dunnololda.scage.support.Vec 4 | import com.github.dunnololda.cli.AppProperties._ 5 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 6 | import com.github.dunnololda.scage.handlers.RendererLib 7 | import collection.mutable.ArrayBuffer 8 | import collection.mutable 9 | import scala.language.implicitConversions 10 | 11 | class ScageTracer[T <: TraceTrait](val field_from_x:Int = property("field.from.x", 0), 12 | val field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}), 13 | val field_from_y:Int = property("field.from.y", 0), 14 | val field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}), 15 | init_h_x:Int = property("field.h_x", 0), 16 | init_h_y:Int = property("field.h_y", 0), 17 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0, 18 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 800})/50) else 0, 19 | val solid_edges:Boolean = property("field.solid_edges", true)) { 20 | private val log = MySimpleLogger(this.getClass.getName) 21 | 22 | val field_width = field_to_x - field_from_x 23 | val field_height = field_to_y - field_from_y 24 | 25 | val N_x = if(init_h_x != 0) field_width/init_h_x else init_N_x 26 | val N_y = if(init_h_y != 0) field_height/init_h_y else init_N_y 27 | 28 | val h_x = if(init_h_x == 0) field_width/init_N_x else init_h_x 29 | val h_y = if(init_h_y == 0) field_height/init_N_y else init_h_y 30 | 31 | log.info("creating tracer "+this.getClass.getName+": h_x="+h_x+" h_y="+h_y+" N_x="+N_x+" N_y="+N_y) 32 | 33 | // for client classes - children of ScageTracer 34 | protected def setTraceLocation(trace:T, new_location:Vec) {trace._location = new_location} 35 | protected implicit def trace2updateable(trace:T): Object {def __location_=(new_location: Vec): Unit; def __location: Vec} = new { 36 | def __location = trace._location 37 | def __location_=(new_location:Vec) {trace._location = new_location} 38 | } 39 | 40 | def isPointOnArea(x:Float, y:Float):Boolean = x >= 0 && x < N_x && y >= 0 && y < N_y 41 | def isPointOnArea(point:Vec):Boolean = isPointOnArea(point.x, point.y) 42 | def outsidePoint(point:Vec):Vec = { 43 | def checkC(c:Float, dist:Int):Float = { 44 | if(c >= dist) checkC(c - dist, dist) 45 | else if(c < 0) checkC(c + dist, dist) 46 | else c 47 | } 48 | if(solid_edges) point 49 | else Vec(checkC(point.x, N_x), checkC(point.y, N_y)) 50 | } 51 | 52 | def point(x:Float, y:Float):Vec = Vec(((x - field_from_x)/field_width*N_x).toInt, 53 | ((y - field_from_y)/field_height*N_y).toInt) 54 | def point(p:Vec):Vec = point(p.x, p.y) 55 | def pointCenter(x:Float, y:Float):Vec = Vec(field_from_x + x*h_x + h_x/2, field_from_y + y*h_y + h_y/2) 56 | def pointCenter(p:Vec):Vec = pointCenter(p.x, p.y) 57 | 58 | protected def initMatrix(matrix:Array[Array[ArrayBuffer[T]]]) { 59 | for(i <- 0 until matrix.length; j <- 0 until matrix.head.length) {matrix(i)(j) = ArrayBuffer[T]()} 60 | } 61 | protected def clearMatrix(matrix:Array[Array[ArrayBuffer[T]]]) { 62 | for(i <- 0 until matrix.length; j <- 0 until matrix.head.length) {matrix(i)(j).clear()} 63 | } 64 | 65 | // it is very critical for the structures below to be changed only inside ScageTracer 66 | // but for convenience I keep them protected, so client classes - children of ScageTracer can read them 67 | protected val point_matrix = Array.ofDim[ArrayBuffer[T]](N_x, N_y) 68 | initMatrix(point_matrix) 69 | protected val traces_by_ids = mutable.HashMap[Int, T]() 70 | protected var traces_list = ArrayBuffer[T]() 71 | def tracesList = traces_list.toList 72 | 73 | def addTrace(point:Vec, trace:T) = { 74 | if(isPointOnArea(point)) { 75 | trace._location = point 76 | point_matrix(point.ix)(point.iy) += trace 77 | traces_by_ids += trace.id -> trace 78 | traces_list += trace 79 | log.debug("added new trace #"+trace.id+" in point ("+trace.location+")") 80 | } else log.warn("failed to add trace: point ("+point+") is out of area") 81 | trace 82 | } 83 | def addTraces(traces_in_locations:(Vec, T)*) = { 84 | for { 85 | (location, trace) <- traces_in_locations 86 | } yield addTrace(location, trace) 87 | } 88 | 89 | def containsTrace(trace_id:Int) = traces_by_ids.contains(trace_id) 90 | def containsTrace(trace:T) = traces_by_ids.contains(trace.id) 91 | 92 | protected def _removeTrace(trace_id:Int, show_warn:Boolean) { 93 | traces_by_ids.get(trace_id) match { 94 | case Some(trace) => 95 | point_matrix(trace.location.ix)(trace.location.iy) -= trace 96 | traces_by_ids -= trace.id 97 | traces_list -= trace 98 | log.debug("removed trace #"+trace.id) 99 | case None => if(show_warn) log.warn("trace #"+trace_id+" not found") 100 | } 101 | } 102 | 103 | def removeTrace(trace:T) {_removeTrace(trace.id, show_warn = true)} 104 | def removeTraceNoWarn(trace:T) {_removeTrace(trace.id, show_warn = false)} 105 | def removeTraceById(trace_id:Int) {_removeTrace(trace_id, show_warn = true)} 106 | def removeTraceByIdNoWarn(trace_id:Int) {_removeTrace(trace_id, show_warn = false)} 107 | 108 | // WARNING: its very important not to directly pass contents of point_matrix(trace.location.ix)(trace.location.iy), 109 | // traces_by_ids or traces_list as it cannot remove from itself properly, causing NullPointerException! 110 | // maybe return result (true/false) 111 | // maybe call toList before foreach to copy the list 112 | def removeTraces(traces_to_remove:T*) {traces_to_remove.foreach(t => _removeTrace(t.id, show_warn = true))} 113 | def removeTracesNoWarn(traces_to_remove:T*) {traces_to_remove.foreach(t => _removeTrace(t.id, show_warn = false))} 114 | def removeTracesById(trace_ids:Int*) {trace_ids.foreach(t_id => _removeTrace(t_id, show_warn = true))} 115 | def removeTracesByIdNoWarn(trace_ids:Int*) {trace_ids.foreach(t_id => _removeTrace(t_id, show_warn = false))} 116 | def removeAllTraces() { 117 | clearMatrix(point_matrix) 118 | traces_by_ids.clear() 119 | traces_list.clear() 120 | log.info("deleted all traces") 121 | } 122 | def removeTracesInPoint(point:Vec) {removeTraces(tracesInPoint(point):_*)} 123 | def removeTracesInPoint(x:Int, y:Int) {removeTraces(tracesInPoint(x, y):_*)} 124 | 125 | def tracesInPoint(point:Vec, condition:T => Boolean):List[T] = tracesInPoint(point.ix, point.iy, condition) 126 | def tracesInPoint(x:Int, y:Int, condition:T => Boolean) = { 127 | if(!isPointOnArea(x, y)) Nil 128 | else { 129 | (for { 130 | trace <- point_matrix(x)(y) 131 | if condition(trace) 132 | } yield trace).toList 133 | } 134 | } 135 | 136 | def tracesInPoint(point:Vec):List[T] = tracesInPoint(point.ix, point.iy) 137 | def tracesInPoint(x:Int, y:Int) = { 138 | if(!isPointOnArea(x, y)) Nil 139 | else point_matrix(x)(y).toList 140 | } 141 | 142 | def tracesInPointRange(xrange:Range, yrange:Range, condition:T => Boolean):IndexedSeq[T] = tracesNearPoint(Vec.zero, xrange, yrange, condition) 143 | def tracesInPointRange(xrange:Range, condition:T => Boolean):IndexedSeq[T] = tracesNearPoint(Vec.zero, xrange, xrange, condition) 144 | def tracesInPointRange(xrange:Range, yrange:Range):IndexedSeq[T] = tracesNearPoint(Vec.zero, xrange, yrange) 145 | def tracesInPointRange(xrange:Range):IndexedSeq[T] = tracesNearPoint(Vec.zero, xrange, xrange) 146 | 147 | def tracesNearPoint(point:Vec, xrange:Range, yrange:Range, condition:T => Boolean):IndexedSeq[T] = { 148 | for { 149 | i <- xrange 150 | j <- yrange 151 | near_point = outsidePoint(point + Vec(i, j)) 152 | if isPointOnArea(near_point) 153 | trace <- tracesInPoint(near_point, condition) 154 | } yield trace 155 | } 156 | def tracesNearPoint(point:Vec, xrange:Range, condition:T => Boolean):IndexedSeq[T] = tracesNearPoint(point, xrange, xrange, condition) 157 | def tracesNearPoint(point:Vec, xrange:Range, yrange:Range):IndexedSeq[T] = { 158 | for { 159 | i <- xrange 160 | j <- yrange 161 | near_point = outsidePoint(point + Vec(i, j)) 162 | if isPointOnArea(near_point) 163 | trace <- tracesInPoint(near_point) 164 | } yield trace 165 | } 166 | def tracesNearPoint(point:Vec, xrange:Range):IndexedSeq[T] = tracesNearPoint(point, xrange, xrange) 167 | 168 | val LOCATION_UPDATED = 0 169 | val SAME_LOCATION = 1 170 | val OUT_OF_AREA = 2 171 | val TRACE_NOT_FOUND = 3 172 | def updateLocation(trace_id:Int, new_point:Vec):Int = { // TODO: maybe return tuple (new_location, operation_status), maybe rename 173 | traces_by_ids.get(trace_id) match { 174 | case Some(updateable_trace) => 175 | val old_point = updateable_trace.location 176 | val new_point_edges_affected = outsidePoint(new_point) 177 | if(isPointOnArea(new_point_edges_affected)) { 178 | if(old_point != new_point_edges_affected) { 179 | point_matrix(old_point.ix)(old_point.iy) -= updateable_trace 180 | point_matrix(new_point_edges_affected.ix)(new_point_edges_affected.iy) += updateable_trace 181 | updateable_trace._location = new_point_edges_affected 182 | LOCATION_UPDATED 183 | } else { 184 | //log.warn("didn't update trace "+trace.id+": new point is the same as the old one") // don'tknow exactly if I need such debug message 185 | SAME_LOCATION 186 | } 187 | } else { 188 | log.debug("failed to update trace "+trace_id+": new point is out of area") 189 | OUT_OF_AREA 190 | } 191 | case None => 192 | log.warn("trace with id "+trace_id+" not found") 193 | TRACE_NOT_FOUND 194 | } 195 | } 196 | def updateLocation(trace:T, new_point:Vec):Int = updateLocation(trace.id, new_point) // TODO: maybe rename 197 | 198 | def moveTrace(trace:T, delta:Vec) = { 199 | updateLocation(trace.id, trace.location + delta) 200 | } 201 | 202 | def randomPoint(leftup_x:Int = 0, leftup_y:Int = N_y-1, width:Int = N_x, height:Int = N_y) = { 203 | val x = leftup_x + (math.random*width).toInt 204 | val y = leftup_y - (math.random*height).toInt 205 | Vec(x,y) 206 | } 207 | def randomPointWithCondition(leftup_x:Int = 0, 208 | leftup_y:Int = N_y-1, 209 | width:Int = N_x, 210 | height:Int = N_y, 211 | condition:Vec => Boolean, 212 | num_tries:Int = 10):Option[Vec] = { 213 | (0 until num_tries).view.map(i => randomPoint()).find(p => condition(p)) 214 | } 215 | def randomCoord(leftup_x:Int = field_from_x, leftup_y:Int = field_to_y-1, width:Int = field_to_x - field_from_x, height:Int = field_to_y - field_from_y) = { 216 | pointCenter(point(randomPoint(leftup_x, leftup_y, width, height))) 217 | } 218 | def randomCoordWithCondition(leftup_x:Int = field_from_x, 219 | leftup_y:Int = field_to_y-1, 220 | width:Int = field_to_x - field_from_x, 221 | height:Int = field_to_y - field_from_y, 222 | condition:Vec => Boolean, 223 | num_tries:Int = 10):Option[Vec] = { 224 | (0 until num_tries).view.map(i => randomCoord()).find(coord => condition(coord)) 225 | } 226 | 227 | lazy val trace_grid = { 228 | val x_lines = (field_from_x to field_to_x by h_x).foldLeft(List[Vec]())((lines, x) => Vec(x, field_from_y) :: Vec(x, field_to_y) :: lines) 229 | val y_lines = (field_from_y to field_to_y by h_y).foldLeft(List[Vec]())((lines, y) => Vec(field_from_x, y) :: Vec(field_to_x, y) :: lines) 230 | x_lines ::: y_lines 231 | } 232 | } 233 | 234 | object ScageTracer { 235 | def apply(field_from_x:Int = property("field.from.x", 0), 236 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}), 237 | field_from_y:Int = property("field.from.y", 0), 238 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}), 239 | init_h_x:Int = property("field.h_x", 0), 240 | init_h_y:Int = property("field.h_y", 0), 241 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0, 242 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0, 243 | solid_edges:Boolean = property("field.solid_edges", true)) = { 244 | new ScageTracer[Trace](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges) { 245 | def addTrace(point:Vec):Trace = {addTrace(point, Trace())} 246 | } 247 | } 248 | 249 | // maybe some other name for this factory method (like 'newTracer', etc) 250 | def create[T <: TraceTrait](field_from_x:Int = property("field.from.x", 0), 251 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}), 252 | field_from_y:Int = property("field.from.y", 0), 253 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}), 254 | init_h_x:Int = property("field.h_x", 0), 255 | init_h_y:Int = property("field.h_y", 0), 256 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0, 257 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0, 258 | solid_edges:Boolean = property("field.solid_edges", true)) = { 259 | new ScageTracer[T](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges) 260 | } 261 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/ScageScreen.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage 2 | 3 | import java.applet.Applet 4 | import java.awt.{BorderLayout, Canvas} 5 | import com.github.dunnololda.cli.Imports._ 6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 7 | import com.github.dunnololda.scage.handlers._ 8 | import com.github.dunnololda.scage.handlers.controller2.{ScageController, SingleController} 9 | import com.github.dunnololda.scage.handlers.controller3.{ActorSingleController, ControllerActorSystem} 10 | import com.github.dunnololda.scage.support.XInitThreadsCaller.xInitThreads 11 | import org.lwjgl.opengl.Display 12 | 13 | // abstract classes instead of traits to make it easy to use with MultiController 14 | abstract class Screen(val unit_name: String = "Scage Screen") extends Scage with Renderer with ScageController { 15 | private val log = MySimpleLogger(this.getClass.getName) 16 | 17 | override def run(): Unit = { 18 | log.info("starting screen " + unit_name + "...") 19 | executePreinits() 20 | executeInits() 21 | is_running = true 22 | prepareRendering() 23 | log.info(unit_name + ": run") 24 | while (is_running && Scage.isAppRunning) { 25 | checkControlsAndExecuteActions() 26 | performRendering() 27 | } 28 | executeClears() 29 | executeDisposes() 30 | scage_log.info(unit_name + " was stopped") 31 | } 32 | } 33 | 34 | abstract class ScreenD(val unit_name: String = "Scage Screen") extends Scage with RendererD with ScageController { 35 | private val log = MySimpleLogger(this.getClass.getName) 36 | 37 | override def run(): Unit = { 38 | log.info("starting screen " + unit_name + "...") 39 | executePreinits() 40 | executeInits() 41 | is_running = true 42 | prepareRendering() 43 | log.info(unit_name + ": run") 44 | while (is_running && Scage.isAppRunning) { 45 | checkControlsAndExecuteActions() 46 | performRendering() 47 | } 48 | executeClears() 49 | executeDisposes() 50 | scage_log.info(unit_name + " was stopped") 51 | } 52 | } 53 | 54 | abstract class ScreenApp( 55 | title: String = property("app.name", "Scage App"), 56 | width: Int = property("screen.width", 800), 57 | height: Int = property("screen.height", 600) 58 | ) extends Screen(title) with Cli { 59 | val app_start_moment = System.currentTimeMillis() 60 | 61 | def msecsFromAppStart: Long = System.currentTimeMillis() - app_start_moment 62 | 63 | override def run(): Unit = { 64 | executePreinits() 65 | executeInits() 66 | is_running = true 67 | prepareRendering() 68 | scage_log.info(unit_name + ": run") 69 | while (is_running && Scage.isAppRunning) { 70 | checkControlsAndExecuteActions() 71 | performRendering() 72 | } 73 | RendererLib.renderExitMessage() 74 | executeClears() 75 | executeDisposes() 76 | } 77 | 78 | override def main(args: Array[String]) { 79 | scage_log.info("starting main screen " + title + "...") 80 | RendererLib.initgl(width, height, title) 81 | RendererLib.drawWelcomeMessages() 82 | super.main(args) 83 | run() 84 | RendererLib.destroygl() 85 | scage_log.info(title + " was stopped") 86 | System.exit(0) // need explicit exit for the app's utilizing NetServer/NetClient as they have actors 87 | } 88 | } 89 | 90 | abstract class ScreenAppD( 91 | title: String = property("app.name", "Scage App"), 92 | width: Int = property("screen.width", 800), 93 | height: Int = property("screen.height", 600) 94 | ) extends ScreenD(title) with Cli { 95 | val app_start_moment = System.currentTimeMillis() 96 | 97 | def msecsFromAppStart: Long = System.currentTimeMillis() - app_start_moment 98 | 99 | override def run(): Unit = { 100 | executePreinits() 101 | executeInits() 102 | is_running = true 103 | prepareRendering() 104 | scage_log.info(unit_name + ": run") 105 | while (is_running && Scage.isAppRunning) { 106 | checkControlsAndExecuteActions() 107 | performRendering() 108 | } 109 | RendererLibD.renderExitMessage() 110 | executeClears() 111 | executeDisposes() 112 | } 113 | 114 | override def main(args: Array[String]) { 115 | scage_log.info("starting main screen " + title + "...") 116 | RendererLibD.initgl(width, height, title) 117 | RendererLibD.drawWelcomeMessages() 118 | super.main(args) 119 | run() 120 | RendererLibD.destroygl() 121 | scage_log.info(title + " was stopped") 122 | System.exit(0) // need explicit exit for the app's utilizing NetServer/NetClient as they have actors 123 | } 124 | } 125 | 126 | abstract class ScreenAppMT( 127 | title: String = property("app.name", "Scage App"), 128 | width: Int = property("screen.width", 800), 129 | height: Int = property("screen.height", 600) 130 | ) extends Screen(title) with Cli { 131 | val app_start_moment = System.currentTimeMillis() 132 | 133 | def msecsFromAppStart: Long = System.currentTimeMillis() - app_start_moment 134 | 135 | override def run(): Unit = { 136 | executePreinits() 137 | executeInits() 138 | is_running = true 139 | prepareRendering() 140 | scage_log.info(unit_name + ": run") 141 | while (is_running && Scage.isAppRunning) { 142 | checkControlsAndExecuteActions() 143 | performRendering() 144 | } 145 | RendererLib.renderExitMessage() 146 | executeClears() 147 | executeDisposes() 148 | } 149 | 150 | override def main(args: Array[String]) { 151 | scage_log.info("starting main screen " + title + "...") 152 | xInitThreads(scage_log) 153 | ControllerActorSystem.createControllerActor() 154 | ControllerActorSystem.initGLAndReleaseContext(width, height, title) 155 | Display.makeCurrent() 156 | RendererLib.drawWelcomeMessages() 157 | ControllerActorSystem.startCheckControls() 158 | super.main(args) 159 | run() 160 | ControllerActorSystem.shutDownAndAwaitTermination() 161 | RendererLib.destroygl() 162 | scage_log.info(title + " was stopped") 163 | //System.exit(0) // need explicit exit for the app's utilizing NetServer/NetClient as they have actors 164 | } 165 | } 166 | 167 | abstract class ScreenAppDMT( 168 | title: String = property("app.name", "Scage App"), 169 | width: Int = property("screen.width", 800), 170 | height: Int = property("screen.height", 600) 171 | ) extends ScreenD(title) with Cli { 172 | val app_start_moment = System.currentTimeMillis() 173 | 174 | def msecsFromAppStart: Long = System.currentTimeMillis() - app_start_moment 175 | 176 | override def run(): Unit = { 177 | executePreinits() 178 | executeInits() 179 | is_running = true 180 | prepareRendering() 181 | scage_log.info(unit_name + ": run") 182 | while (is_running && Scage.isAppRunning) { 183 | checkControlsAndExecuteActions() 184 | performRendering() 185 | } 186 | RendererLibD.renderExitMessage() 187 | executeClears() 188 | executeDisposes() 189 | } 190 | 191 | override def main(args: Array[String]) { 192 | scage_log.info("starting main screen " + title + "...") 193 | //xInitThreads(scage_log) 194 | ControllerActorSystem.createControllerActor() 195 | ControllerActorSystem.initGLAndReleaseContext(width, height, title) 196 | Display.makeCurrent() 197 | RendererLib.drawWelcomeMessages() 198 | ControllerActorSystem.startCheckControls() 199 | super.main(args) 200 | run() 201 | ControllerActorSystem.shutDownAndAwaitTermination() 202 | RendererLibD.destroygl() 203 | scage_log.info(title + " was stopped") 204 | System.exit(0) // need explicit exit for the app's utilizing NetServer/NetClient as they have actors 205 | } 206 | } 207 | 208 | 209 | class ScageScreen(unit_name: String = "Scage Screen") extends Screen(unit_name) with SingleController 210 | 211 | class ScageScreenD(unit_name: String = "Scage Screen") extends ScreenD(unit_name) with SingleController 212 | 213 | class ScageScreenMT(unit_name: String = "Scage Screen") extends Screen(unit_name) with ActorSingleController 214 | 215 | class ScageScreenDMT(unit_name: String = "Scage Screen") extends ScreenD(unit_name) with ActorSingleController 216 | 217 | class ScageScreenApp(title: String = property("app.name", "Scage App"), 218 | width: Int = property("screen.width", 800), 219 | height: Int = property("screen.height", 600)) extends ScreenApp(title, width, height) with SingleController 220 | 221 | class ScageScreenAppD(title: String = property("app.name", "Scage App"), 222 | width: Int = property("screen.width", 800), 223 | height: Int = property("screen.height", 600)) extends ScreenAppD(title, width, height) with SingleController 224 | 225 | class ScageScreenAppMT(title: String = property("app.name", "Scage App"), 226 | width: Int = property("screen.width", 800), 227 | height: Int = property("screen.height", 600)) extends ScreenAppMT(title, width, height) with ActorSingleController 228 | 229 | class ScageScreenAppDMT(title: String = property("app.name", "Scage App"), 230 | width: Int = property("screen.width", 800), 231 | height: Int = property("screen.height", 600)) extends ScreenAppDMT(title, width, height) with ActorSingleController 232 | 233 | abstract class ScageApplet extends Applet { 234 | def screen: ScageScreenApp 235 | 236 | /** The Canvas where the LWJGL Display is added */ 237 | private var display_parent: Canvas = _ 238 | 239 | /** Thread which runs the main game loop */ 240 | private var gameThread: Thread = _ 241 | 242 | /** 243 | * Once the Canvas is created its add notify method will call this method to 244 | * start the LWJGL Display and game loop in another thread. 245 | */ 246 | private def startScage(): Unit = { 247 | gameThread = new Thread { 248 | override def run(): Unit = { 249 | Display.setParent(display_parent) 250 | screen.main(Array[String]()) 251 | } 252 | } 253 | gameThread.start() 254 | } 255 | 256 | /** 257 | * Tell game loop to stop running, after which the LWJGL Display will be destoryed. 258 | * The main thread will wait for the Display.destroy() to complete 259 | */ 260 | private def stopScage(): Unit = { 261 | screen.stop() 262 | try { 263 | gameThread.join() 264 | } catch { 265 | case e: InterruptedException => 266 | e.printStackTrace() 267 | } 268 | } 269 | 270 | /** 271 | * Applet Destroy method will remove the canvas, before canvas is destroyed it will notify 272 | * stopLWJGL() to stop main game loop and to destroy the Display 273 | */ 274 | override def destroy(): Unit = { 275 | remove(display_parent) 276 | super.destroy() 277 | } 278 | 279 | /** 280 | * initialise applet by adding a canvas to it, this canvas will start the LWJGL Display and game loop 281 | * in another thread. It will also stop the game loop and destroy the display on canvas removal when 282 | * applet is destroyed. 283 | */ 284 | override def init(): Unit = { 285 | setLayout(new BorderLayout()) 286 | try { 287 | display_parent = new Canvas() { 288 | override def addNotify(): Unit = { 289 | super.addNotify() 290 | startScage() 291 | } 292 | 293 | override def removeNotify(): Unit = { 294 | stopScage() 295 | super.removeNotify() 296 | } 297 | } 298 | display_parent.setSize(getWidth, getHeight) 299 | add(display_parent) 300 | display_parent.setFocusable(true) 301 | display_parent.requestFocus() 302 | display_parent.setIgnoreRepaint(true) 303 | setVisible(true) 304 | } catch { 305 | case e: Exception => 306 | System.err.println(e) 307 | throw new RuntimeException("Unable to create display") 308 | } 309 | } 310 | } 311 | 312 | abstract class ScageAppletD extends Applet { 313 | def screen: ScageScreenAppD 314 | 315 | /** The Canvas where the LWJGL Display is added */ 316 | private var display_parent: Canvas = _ 317 | 318 | /** Thread which runs the main game loop */ 319 | private var gameThread: Thread = _ 320 | 321 | /** 322 | * Once the Canvas is created its add notify method will call this method to 323 | * start the LWJGL Display and game loop in another thread. 324 | */ 325 | private def startScage(): Unit = { 326 | gameThread = new Thread { 327 | override def run(): Unit = { 328 | Display.setParent(display_parent) 329 | screen.main(Array[String]()) 330 | } 331 | } 332 | gameThread.start() 333 | } 334 | 335 | /** 336 | * Tell game loop to stop running, after which the LWJGL Display will be destoryed. 337 | * The main thread will wait for the Display.destroy() to complete 338 | */ 339 | private def stopScage(): Unit = { 340 | screen.stop() 341 | try { 342 | gameThread.join() 343 | } catch { 344 | case e: InterruptedException => 345 | e.printStackTrace() 346 | } 347 | } 348 | 349 | /** 350 | * Applet Destroy method will remove the canvas, before canvas is destroyed it will notify 351 | * stopLWJGL() to stop main game loop and to destroy the Display 352 | */ 353 | override def destroy(): Unit = { 354 | remove(display_parent) 355 | super.destroy() 356 | } 357 | 358 | /** 359 | * initialise applet by adding a canvas to it, this canvas will start the LWJGL Display and game loop 360 | * in another thread. It will also stop the game loop and destroy the display on canvas removal when 361 | * applet is destroyed. 362 | */ 363 | override def init(): Unit = { 364 | setLayout(new BorderLayout()) 365 | try { 366 | display_parent = new Canvas() { 367 | override def addNotify(): Unit = { 368 | super.addNotify() 369 | startScage() 370 | } 371 | 372 | override def removeNotify(): Unit = { 373 | stopScage() 374 | super.removeNotify() 375 | } 376 | } 377 | display_parent.setSize(getWidth, getHeight) 378 | add(display_parent) 379 | display_parent.setFocusable(true) 380 | display_parent.requestFocus() 381 | display_parent.setIgnoreRepaint(true) 382 | setVisible(true) 383 | } catch { 384 | case e: Exception => 385 | System.err.println(e) 386 | throw new RuntimeException("Unable to create display") 387 | } 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/ScageLib.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage 2 | 3 | import handlers.RendererLib 4 | import support._ 5 | import support.messages._ 6 | import com.github.dunnololda.cli.AppProperties 7 | import net.phys2d.math.ROVector2f 8 | import scala.language.implicitConversions 9 | 10 | object ScageLib extends ScageMessageTrait with ScageXMLTrait with RendererLib with LWJGLKeyboard with ScageColorTrait with ScageIdTrait with EventsTrait { 11 | def property[A: Manifest](key: String, default: => A): A = AppProperties.property(key, default) 12 | 13 | def optProperty[A: Manifest](key: String): Option[A] = AppProperties.optProperty(key) 14 | 15 | def reqProperty[A: Manifest](key: String): A = AppProperties.reqProperty(key) 16 | 17 | def property[A: Manifest](key: String, default: => A, condition: (A => (Boolean, String))): A = AppProperties.property(key, default, condition) 18 | 19 | def appName = AppProperties.appName 20 | 21 | def appVersion = AppProperties.appVersion 22 | 23 | def print(message: Any, x: Float, y: Float, size: Float, color: ScageColor, align: String) { 24 | ScageMessage.print(message, x, y, size, color, align) 25 | } 26 | 27 | def messageBounds(message: Any, size: Float = max_font_size): Vec = ScageMessage.messageBounds(message, size) 28 | 29 | def areaForMessage(message: Any, coord: Vec, size: Float = max_font_size, align: String = "center"): Seq[Vec] = ScageMessage.areaForMessage(message, coord, size, align) 30 | 31 | def addGlyphs(from: Int, to: Int) { 32 | ScageMessage.addGlyphs(from, to) 33 | } 34 | 35 | def addGlyphs(text: String) { 36 | ScageMessage.addGlyphs(text) 37 | } 38 | 39 | def lang = ScageXML.lang 40 | 41 | def lang_=(new_lang: String) { 42 | ScageXML.lang = new_lang 43 | } 44 | 45 | def messagesBase = ScageXML.messagesBase 46 | 47 | def messagesBase_=(new_base: String) { 48 | ScageXML.messagesBase = new_base 49 | } 50 | 51 | def interfacesBase = ScageXML.interfacesBase 52 | 53 | def interfacesBase_=(new_base: String) { 54 | ScageXML.interfacesBase = new_base 55 | } 56 | 57 | def messagesFile: String = ScageXML.messagesFile 58 | 59 | def interfacesFile: String = ScageXML.interfacesFile 60 | 61 | def xml(message_id: String, parameters: Any*): String = ScageXML.xml(message_id, parameters: _*) 62 | 63 | def xmlOrDefault(message_id: String, parameters: Any*): String = ScageXML.xmlOrDefault(message_id, parameters: _*) 64 | 65 | def xmlInterface(interface_id: String, parameters: Any*): Array[MessageData] = ScageXML.xmlInterface(interface_id, parameters: _*) 66 | 67 | def xmlInterfaceStrings(interface_id: String, parameters: Any*): Array[String] = ScageXML.xmlInterfaceStrings(interface_id, parameters: _*) 68 | 69 | def onEventWithArguments(event_name: String)(event_action: PartialFunction[Any, Unit]): (String, Int) = Events.onEventWithArguments(event_name)(event_action) 70 | 71 | def onEvent(event_name: String)(event_action: => Unit): (String, Int) = Events.onEvent(event_name)(event_action) 72 | 73 | def callEvent(event_name: String, arg: Any) { 74 | Events.callEvent(event_name, arg) 75 | } 76 | 77 | def callEvent(event_name: String) { 78 | Events.callEvent(event_name) 79 | } 80 | 81 | def delEvents(event_ids: (String, Int)*) { 82 | Events.delEvents(event_ids: _*) 83 | } 84 | 85 | def isAppRunning: Boolean = Scage.isAppRunning 86 | 87 | def stopApp() { 88 | Scage.stopApp() 89 | } 90 | 91 | def nextId = ScageId.nextId 92 | 93 | // implicits 94 | 95 | implicit class Int2Vecrich(k: Int) { 96 | def *(v: Vec) = v * k 97 | 98 | def /(v: Vec) = v / k 99 | 100 | def */(v: Vec) = Vec(-v.y * k, v.x * k) 101 | 102 | def toRad: Float = k / 180f * math.Pi.toFloat 103 | 104 | def toDeg: Float = k / math.Pi.toFloat * 180f 105 | } 106 | 107 | implicit class Long2Vecrich(k: Long) { 108 | def *(v: Vec) = v * k 109 | 110 | def /(v: Vec) = v / k 111 | 112 | def */(v: Vec) = Vec(-v.y * k, v.x * k) 113 | 114 | def toRad: Float = k / 180f * math.Pi.toFloat 115 | 116 | def toDeg: Float = k / math.Pi.toFloat * 180f 117 | } 118 | 119 | implicit class Float2Vecrich(k: Float) { 120 | def *(v: Vec) = v * k 121 | 122 | def /(v: Vec) = v / k 123 | 124 | def toRad: Float = k / 180f * math.Pi.toFloat 125 | 126 | def toDeg: Float = k / math.Pi.toFloat * 180f 127 | } 128 | 129 | implicit class Double2Vecrich(k: Double) { 130 | def *(v: Vec) = v * k 131 | 132 | def /(v: Vec) = v / k 133 | 134 | def */(v: Vec) = Vec(-v.y * k, v.x * k) 135 | 136 | def toRad: Double = k / 180.0 * math.Pi 137 | 138 | def toDeg: Double = k / math.Pi * 180.0 139 | } 140 | 141 | implicit class Int2DVecrich(k: Int) { 142 | def *(v: DVec) = v * k 143 | 144 | def /(v: DVec) = v / k 145 | 146 | def */(v: DVec) = DVec(-v.y * k, v.x * k) 147 | } 148 | 149 | implicit class Long2DVecrich(k: Long) { 150 | def *(v: DVec) = v * k 151 | 152 | def /(v: DVec) = v / k 153 | 154 | def */(v: DVec) = DVec(-v.y * k, v.x * k) 155 | } 156 | 157 | implicit class Float2DVecrich(k: Float) { 158 | def *(v: DVec) = v * k 159 | 160 | def /(v: DVec) = v / k 161 | 162 | def */(v: DVec) = DVec(-v.y * k, v.x * k) 163 | } 164 | 165 | implicit class Double2DVecrich(k: Double) { 166 | def *(v: DVec) = v * k 167 | 168 | def /(v: DVec) = v / k 169 | 170 | def */(v: DVec) = DVec(-v.y * k, v.x * k) 171 | } 172 | 173 | implicit class Phys2dVec2Vec(pv: ROVector2f) { 174 | def toVec: Vec = Vec(pv.getX, pv.getY) 175 | 176 | def toDVec: DVec = DVec(pv.getX, pv.getY) 177 | } 178 | 179 | implicit def Vec2dvec(v: Vec): DVec = v.toDVec 180 | 181 | implicit def DVec2Vec(dv: Vec): Vec = dv.toVec 182 | 183 | implicit val NumericVec = new Numeric[Vec] { 184 | def plus(x: ScageLib.Vec, y: ScageLib.Vec): ScageLib.Vec = x + y 185 | 186 | def minus(x: ScageLib.Vec, y: ScageLib.Vec): ScageLib.Vec = x - y 187 | 188 | def times(x: ScageLib.Vec, y: ScageLib.Vec): ScageLib.Vec = ??? 189 | 190 | def negate(x: ScageLib.Vec): ScageLib.Vec = x * (-1) 191 | 192 | def fromInt(x: Int): ScageLib.Vec = Vec(x, x) 193 | 194 | def toInt(x: ScageLib.Vec): Int = x.ix 195 | 196 | def toLong(x: ScageLib.Vec): Long = x.ix 197 | 198 | def toFloat(x: ScageLib.Vec): Float = x.x 199 | 200 | def toDouble(x: ScageLib.Vec): Double = x.x 201 | 202 | def compare(x: ScageLib.Vec, y: ScageLib.Vec): Int = ??? 203 | } 204 | 205 | implicit val NumericDVec = new Numeric[DVec] { 206 | def plus(x: ScageLib.DVec, y: ScageLib.DVec): ScageLib.DVec = x + y 207 | 208 | def minus(x: ScageLib.DVec, y: ScageLib.DVec): ScageLib.DVec = x - y 209 | 210 | def times(x: ScageLib.DVec, y: ScageLib.DVec): ScageLib.DVec = ??? 211 | 212 | def negate(x: ScageLib.DVec): ScageLib.DVec = x * (-1) 213 | 214 | def fromInt(x: Int): ScageLib.DVec = DVec(x, x) 215 | 216 | def toInt(x: ScageLib.DVec): Int = x.ix 217 | 218 | def toLong(x: ScageLib.DVec): Long = x.ix 219 | 220 | def toFloat(x: ScageLib.DVec): Float = x.x.toFloat 221 | 222 | def toDouble(x: ScageLib.DVec): Double = x.x 223 | 224 | def compare(x: ScageLib.DVec, y: ScageLib.DVec): Int = ??? 225 | } 226 | 227 | implicit class MinOption[A](s: Seq[A]) { 228 | def minOption(implicit o: Ordering[A]): Option[A] = if (s.isEmpty) None else Some(s.min(o)) 229 | 230 | def minOptionBy[B](f: A => B)(implicit o: Ordering[B]): Option[A] = if (s.isEmpty) None else Some(s.minBy(f)(o)) 231 | } 232 | 233 | // support 234 | 235 | def msecs = System.currentTimeMillis() 236 | 237 | def msecsFrom(moment: Long) = System.currentTimeMillis() - moment 238 | 239 | // Vec/DVec helper methods 240 | 241 | def areLinesIntersect(p: Vec, p2: Vec, q: Vec, q2: Vec): Boolean = { 242 | // http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect 243 | val r = p2 - p 244 | val s = q2 - q 245 | val r_cross_s = r */ s 246 | if (r_cross_s == 0) { 247 | val q_min_p_cross_r = (q - p) */ s 248 | if (q_min_p_cross_r == 0) { 249 | // два отрезка коллинеарны 250 | val t0 = (q - p) * r / (r * r) 251 | val t1 = t0 + (s * r) / (r * r) 252 | (t0 <= 0 && t1 >= 0) || (t0 >= 0 && t0 <= 1) 253 | } else { 254 | // два отрезка параллельны, не пересекаются 255 | false 256 | } 257 | } else { 258 | val t = ((q - p) */ s) / (r */ s) 259 | val u = ((q - p) */ r) / (r */ s) 260 | -1E-14 <= t && t <= 1.00000000000001 && -1E-14 <= u && u <= 1.00000000000001 261 | } 262 | 263 | /*val common = (a2.x - a1.x) * (b2.y - b1.y) - (a2.y - a1.y) * (b2.x - b1.x) 264 | common != 0 && { 265 | val rH = (a1.y - b1.y) * (b2.x - b1.x) - (a1.x - b1.x) * (b2.y - b1.y) 266 | val sH = (a1.y - b1.y) * (a2.x - a1.x) - (a1.x - b1.x) * (a2.y - a1.y) 267 | 268 | val r = rH / common 269 | val s = sH / common 270 | 271 | r >= 0 && r <= 1 && s >= 0 && s <= 1 272 | }*/ 273 | } 274 | 275 | def areLinesIntersect(p: DVec, p2: DVec, q: DVec, q2: DVec): Boolean = { 276 | // http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect 277 | val r = p2 - p 278 | val s = q2 - q 279 | val r_cross_s = r */ s 280 | if (r_cross_s == 0) { 281 | val q_min_p_cross_r = (q - p) */ s 282 | if (q_min_p_cross_r == 0) { 283 | // два отрезка коллинеарны 284 | val t0 = (q - p) * r / (r * r) 285 | val t1 = t0 + (s * r) / (r * r) 286 | (t0 <= 0 && t1 >= 0) || (t0 >= 0 && t0 <= 1) 287 | } else { 288 | // два отрезка параллельны, не пересекаются 289 | false 290 | } 291 | } else { 292 | val t = ((q - p) */ s) / (r */ s) 293 | val u = ((q - p) */ r) / (r */ s) 294 | -1E-14 <= t && t <= 1.00000000000001 && -1E-14 <= u && u <= 1.00000000000001 295 | } 296 | 297 | /*val common = (a2.x - a1.x) * (b2.y - b1.y) - (a2.y - a1.y) * (b2.x - b1.x) 298 | common != 0 && { 299 | val rH = (a1.y - b1.y) * (b2.x - b1.x) - (a1.x - b1.x) * (b2.y - b1.y) 300 | val sH = (a1.y - b1.y) * (a2.x - a1.x) - (a1.x - b1.x) * (a2.y - a1.y) 301 | 302 | val r = rH / common 303 | val s = sH / common 304 | 305 | r >= 0 && r <= 1 && s >= 0 && s <= 1 306 | }*/ 307 | } 308 | 309 | def coordOnRect(coord: Vec, leftup: Vec, width: Float, height: Float): Boolean = { 310 | coord.x >= leftup.x && coord.x < leftup.x + width && 311 | coord.y >= leftup.y - height && coord.y < leftup.y 312 | } 313 | 314 | def coordOnRectCentered(coord: Vec, center: Vec, width: Float, height: Float): Boolean = { 315 | coord.x >= (center.x - width / 2) && coord.x < (center.x + width / 2) && 316 | coord.y >= (center.y - height / 2) && coord.y < (center.y + height / 2) 317 | } 318 | 319 | def coordOnArea(coord: Vec, area: Seq[Vec]): Boolean = { 320 | if (area.length < 2) false 321 | else { 322 | val (leftup, width, height) = { 323 | val (min_x, max_x, min_y, max_y) = area.foldLeft((Float.MaxValue, 0f, Float.MaxValue, 0f)) { 324 | case ((minx, maxx, miny, maxy), Vec(x, y)) => 325 | (if (x < minx) x else minx, 326 | if (x > maxx) x else maxx, 327 | if (y < miny) y else miny, 328 | if (y > maxy) y else maxy) 329 | } 330 | val l = Vec(min_x, max_y) 331 | val w = max_x - min_x 332 | val h = max_y - min_y 333 | (l, w, h) 334 | } 335 | if (!coordOnRect(coord, leftup, width, height)) false 336 | else { 337 | val a1 = coord 338 | val a2 = Vec(Integer.MAX_VALUE, coord.y) 339 | val intersections = (Seq(area.last) ++ area.init).zip(area).foldLeft(0) { 340 | case (result, (b1, b2)) => if (areLinesIntersect(a1, a2, b1, b2)) result + 1 else result 341 | } 342 | intersections % 2 != 0 343 | } 344 | } 345 | } 346 | 347 | def coordOnRect(coord: DVec, leftup: DVec, width: Double, height: Double): Boolean = { 348 | coord.x >= leftup.x && coord.x < leftup.x + width && 349 | coord.y >= leftup.y - height && coord.y < leftup.y 350 | } 351 | 352 | def coordOnRectCentered(coord: DVec, center: DVec, width: Float, height: Float): Boolean = { 353 | coord.x >= (center.x - width / 2) && coord.x < (center.x + width / 2) && 354 | coord.y >= (center.y - height / 2) && coord.y < (center.y + height / 2) 355 | } 356 | 357 | def coordOnArea(coord: DVec, area: Seq[DVec]): Boolean = { 358 | if (area.length < 2) false 359 | else { 360 | val (leftup, width, height) = { 361 | val (min_x, max_x, min_y, max_y) = area.foldLeft((Double.MaxValue, 0.0, Double.MaxValue, 0.0)) { 362 | case ((minx, maxx, miny, maxy), DVec(x, y)) => 363 | (if (x < minx) x else minx, 364 | if (x > maxx) x else maxx, 365 | if (y < miny) y else miny, 366 | if (y > maxy) y else maxy) 367 | } 368 | val l = DVec(min_x, max_y) 369 | val w = max_x - min_x 370 | val h = max_y - min_y 371 | (l, w, h) 372 | } 373 | if (!coordOnRect(coord, leftup, width, height)) false 374 | else { 375 | val a1 = coord 376 | val a2 = DVec(Integer.MAX_VALUE, coord.y) 377 | val intersections = (Seq(area.last) ++ area.init).zip(area).foldLeft(0) { 378 | case (result, (b1, b2)) => if (areLinesIntersect(a1, a2, b1, b2)) result + 1 else result 379 | } 380 | intersections % 2 != 0 381 | } 382 | } 383 | } 384 | 385 | // types 386 | type ScageApp = com.github.dunnololda.scage.ScageApp 387 | type Scage = com.github.dunnololda.scage.Scage 388 | type ScageScreenApp = com.github.dunnololda.scage.ScageScreenApp 389 | type ScageScreen = com.github.dunnololda.scage.ScageScreen 390 | type ScreenApp = com.github.dunnololda.scage.ScreenApp 391 | type Screen = com.github.dunnololda.scage.Screen 392 | type ScreenAppMT = com.github.dunnololda.scage.ScreenAppMT 393 | type ScageScreenMT = com.github.dunnololda.scage.ScageScreenMT 394 | type ScageScreenAppMT = com.github.dunnololda.scage.ScageScreenAppMT 395 | type ScageApplet = com.github.dunnololda.scage.ScageApplet 396 | type Vec = com.github.dunnololda.scage.support.Vec 397 | type DVec = com.github.dunnololda.scage.support.DVec 398 | type SingleController = com.github.dunnololda.scage.handlers.controller2.SingleController 399 | type MultiController = com.github.dunnololda.scage.handlers.controller2.MultiController 400 | type ActorSingleController = com.github.dunnololda.scage.handlers.controller3.ActorSingleController 401 | type ScageColor = com.github.dunnololda.scage.support.ScageColor 402 | type DefaultTrace = com.github.dunnololda.scage.support.tracer3.DefaultTrace 403 | type State = com.github.dunnololda.state.State 404 | type Trace = com.github.dunnololda.scage.support.tracer3.Trace 405 | type TraceTrait = com.github.dunnololda.scage.support.tracer3.TraceTrait 406 | type ScageMessage = com.github.dunnololda.scage.support.messages.ScageMessage 407 | type PathFinder = com.github.dunnololda.scage.support.PathFinder 408 | 409 | type CoordTracer[A <: com.github.dunnololda.scage.support.tracer3.TraceTrait] = com.github.dunnololda.scage.support.tracer3.CoordTracer[A] 410 | type ScageTracer[A <: com.github.dunnololda.scage.support.tracer3.TraceTrait] = com.github.dunnololda.scage.support.tracer3.ScageTracer[A] 411 | 412 | type ScagePhysics = com.github.dunnololda.scage.support.physics.ScagePhysics 413 | 414 | type Physical = com.github.dunnololda.scage.support.physics.Physical 415 | type StaticLine = com.github.dunnololda.scage.support.physics.objects.StaticLine 416 | type StaticPolygon = com.github.dunnololda.scage.support.physics.objects.StaticPolygon 417 | type StaticBox = com.github.dunnololda.scage.support.physics.objects.StaticBox 418 | type StaticBall = com.github.dunnololda.scage.support.physics.objects.StaticBall 419 | type DynaBox = com.github.dunnololda.scage.support.physics.objects.DynaBox 420 | type DynaBall = com.github.dunnololda.scage.support.physics.objects.DynaBall 421 | 422 | val Scage = com.github.dunnololda.scage.Scage 423 | val Vec = com.github.dunnololda.scage.support.Vec 424 | val DVec = com.github.dunnololda.scage.support.DVec 425 | val CoordTracer = com.github.dunnololda.scage.support.tracer3.CoordTracer 426 | val ScageTracer = com.github.dunnololda.scage.support.tracer3.ScageTracer 427 | val ScagePhysics = com.github.dunnololda.scage.support.physics.ScagePhysics 428 | val State = com.github.dunnololda.state.State 429 | val Trace = com.github.dunnololda.scage.support.tracer3.Trace 430 | val ScageColor = com.github.dunnololda.scage.support.ScageColor 431 | val ScageMessage = com.github.dunnololda.scage.support.messages.ScageMessage 432 | val max_font_size = ScageMessage.max_font_size 433 | val PathFinder = com.github.dunnololda.scage.support.PathFinder 434 | } 435 | -------------------------------------------------------------------------------- /src/main/scala/com/github/dunnololda/scage/handlers/controller3/ActorSingleController.scala: -------------------------------------------------------------------------------- 1 | package com.github.dunnololda.scage.handlers.controller3 2 | 3 | import akka.actor.{Actor, ActorSystem, Props} 4 | import akka.pattern.ask 5 | import akka.util.Timeout 6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger 7 | import com.github.dunnololda.scage.Scage 8 | import com.github.dunnololda.scage.handlers.RendererLib 9 | import com.github.dunnololda.scage.handlers.controller2._ 10 | import com.github.dunnololda.scage.support.Vec 11 | import com.typesafe.config.ConfigFactory 12 | import org.lwjgl.input.{Keyboard, Mouse} 13 | import org.lwjgl.opengl.Display 14 | 15 | import scala.collection.mutable 16 | import scala.concurrent.Await 17 | import scala.concurrent.ExecutionContext.Implicits.global 18 | import scala.concurrent.duration._ 19 | 20 | object ControllerActorSystem { 21 | private val controller_system = ActorSystem("controller", ConfigFactory.load("controller-actor-system.conf").getConfig("controller-system")) 22 | 23 | private var controller_actor_created = false 24 | 25 | def createControllerActor(): Unit = { 26 | ControllerActorSystem.controller_system.actorOf(Props(new ControllerActor).withDispatcher("my-pinned-dispatcher"), "controller-actor") 27 | controller_actor_created = true 28 | } 29 | 30 | val controllerActorSelection = { 31 | controller_system.actorSelection("akka://controller/user/controller-actor") 32 | } 33 | 34 | def initGLAndReleaseContext(width: Int, height: Int, title: String) { 35 | Await.result(controllerActorSelection.?(InitGLAndReleaseContext(width, height, title))(Timeout(10000.millis)), 10000.millis) 36 | } 37 | 38 | def startCheckControls(): Unit = { 39 | controllerActorSelection ! StartCheckControls 40 | } 41 | 42 | def stopCheckControls(): Unit = { 43 | Await.result(controllerActorSelection.?(StopCheckControls)(Timeout(10000.millis)), 10000.millis) 44 | } 45 | 46 | def shutdownControllerActor(): Unit = { 47 | Await.result(controllerActorSelection.?(ShutdownControllerActor)(Timeout(10000.millis)), 10000.millis) 48 | } 49 | 50 | def shutDownAndAwaitTermination(): Unit = { 51 | //controllerActorSelection ! ShutdownControllerActor 52 | controller_system.terminate() 53 | Await.result(controller_system.whenTerminated, Duration.Inf) 54 | } 55 | 56 | def monitoredKeysList: List[Int] = { 57 | Await.result(controllerActorSelection.?(GetAllControlledKeys)(Timeout(10000.millis)).mapTo[List[Int]], 10000.millis) 58 | } 59 | 60 | def addKeys(keys: List[Int]): Unit = { 61 | controllerActorSelection ! AddKeys(keys) 62 | } 63 | 64 | def addKey(key_code: Int): Unit = { 65 | controllerActorSelection ! AddKey(key_code) 66 | } 67 | 68 | def removeKey(key_code: Int): Unit = { 69 | controllerActorSelection ! RemoveKey(key_code) 70 | } 71 | 72 | def getInputsHistoryAndReset: List[InputsHistoryMoment] = { 73 | Await.result(controllerActorSelection.?(GetInputsHistoryAndReset)(Timeout(10000.millis)).mapTo[List[InputsHistoryMoment]], 10000.millis) 74 | } 75 | } 76 | 77 | case object CheckControls 78 | 79 | case class AddKey(key_code: Int) 80 | 81 | case class AddKeys(key_codes: List[Int]) 82 | 83 | case class RemoveKey(key_code: Int) 84 | 85 | case object GetInputsHistoryAndReset 86 | 87 | case class GetMouseButtonHistoryAndReset(key_code: Int) 88 | 89 | case object ShutdownControllerActor 90 | 91 | case object StartCheckControls 92 | 93 | case object StopCheckControls 94 | 95 | case class InitGLAndReleaseContext(width: Int, height: Int, title: String) 96 | 97 | case object GetAllControlledKeys 98 | 99 | case class InputsHistoryMoment(moment: Long, keys: Map[Int, Boolean], mouse_coord: Vec, is_mouse_moved: Boolean, dwheel: Int, mouse_buttons: Map[Int, Boolean]) 100 | 101 | class ControllerActor extends Actor { 102 | private val log = MySimpleLogger(this.getClass.getName) 103 | private val inputs_history = mutable.ArrayBuffer[InputsHistoryMoment]() 104 | private val monitored_keys = mutable.HashSet[Int]() 105 | 106 | private var check_controls = false 107 | 108 | override def preStart() { 109 | log.info(s"starting actor ${self.path}") 110 | } 111 | 112 | override def postStop() { 113 | log.info(s"actor ${self.path} died!") 114 | } 115 | 116 | def receive = { 117 | case InitGLAndReleaseContext(width, height, title) => 118 | RendererLib.initgl(width, height, title) 119 | Display.releaseContext() 120 | sender ! true 121 | case AddKey(key_code) => 122 | monitored_keys += key_code 123 | case AddKeys(key_codes) => 124 | monitored_keys ++= key_codes 125 | case RemoveKey(key_code) => 126 | monitored_keys -= key_code 127 | case StartCheckControls => 128 | check_controls = true 129 | self ! CheckControls 130 | case StopCheckControls => 131 | check_controls = false 132 | sender ! true 133 | case CheckControls => 134 | if (check_controls) { 135 | Display.processMessages() 136 | val mouse_coord = Vec(Mouse.getX, Mouse.getY) 137 | val is_mouse_moved = Mouse.getDX != 0 || Mouse.getDY != 0 138 | inputs_history += InputsHistoryMoment(System.currentTimeMillis(), monitored_keys.map { key_code => 139 | (key_code, Keyboard.isKeyDown(key_code)) 140 | }.toMap, mouse_coord, is_mouse_moved, Mouse.getDWheel, Map(0 -> Mouse.isButtonDown(0), 1 -> Mouse.isButtonDown(1))) 141 | context.system.scheduler.scheduleOnce(10.milliseconds) { 142 | self ! CheckControls 143 | } 144 | } 145 | case GetInputsHistoryAndReset => 146 | val history = inputs_history.toList 147 | inputs_history.clear() 148 | sender ! history 149 | case ShutdownControllerActor => 150 | sender ! true 151 | context.system.stop(self) 152 | case GetAllControlledKeys => 153 | sender ! monitored_keys.toList 154 | } 155 | } 156 | 157 | import com.github.dunnololda.scage.handlers.controller3.ControllerActorSystem._ 158 | 159 | trait ActorSingleController extends ScageController { 160 | private val keyboard_key_events = mutable.HashMap[Int, SingleKeyEvent]() 161 | // was_pressed, last_pressed_time, repeat_time, onKeyDown, onKeyUp 162 | private var anykey: () => Any = () => {} 163 | private val mouse_button_events = mutable.HashMap[Int, SingleMouseButtonEvent]() 164 | private var on_mouse_motion: Vec => Any = v => {} 165 | private val on_mouse_drag_motion = mutable.HashMap[Int, Vec => Any]() 166 | private var on_mouse_wheel_up: Vec => Any = v => {} 167 | private var on_mouse_wheel_down: Vec => Any = v => {} 168 | 169 | protected def mappedKeyboardKeys: scala.collection.Set[Int] = keyboard_key_events.keySet 170 | 171 | protected def mappedMouseButtons: scala.collection.Set[Int] = mouse_button_events.keySet 172 | 173 | def key(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}) = { 174 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => if (!on_pause) onKeyDown, () => if (!on_pause) onKeyUp) 175 | addKey(key_code) 176 | control_deletion_operations.addOp(() => { 177 | keyboard_key_events -= key_code 178 | removeKey(key_code) 179 | }, 0) 180 | } 181 | 182 | def keyIgnorePause(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}) = { 183 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => onKeyDown, () => onKeyUp) 184 | addKey(key_code) 185 | control_deletion_operations.addOp(() => { 186 | keyboard_key_events -= key_code 187 | removeKey(key_code) 188 | }, 0) 189 | } 190 | 191 | def keyOnPause(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}): Int = { 192 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => if (on_pause) onKeyDown, () => if (on_pause) onKeyUp) 193 | addKey(key_code) 194 | control_deletion_operations.addOp(() => { 195 | keyboard_key_events -= key_code 196 | removeKey(key_code) 197 | }, 0) 198 | } 199 | 200 | def anykey(onKeyDown: => Any) = { 201 | anykey = () => if (!on_pause) onKeyDown 202 | control_deletion_operations.addOp(() => anykey = () => {}, 0) 203 | } 204 | 205 | def anykeyIgnorePause(onKeyDown: => Any) = { 206 | anykey = () => onKeyDown 207 | control_deletion_operations.addOp(() => anykey = () => {}, 0) 208 | } 209 | 210 | def anykeyOnPause(onKeyDown: => Any) = { 211 | anykey = () => if (on_pause) onKeyDown 212 | control_deletion_operations.addOp(() => anykey = () => {}, 0) 213 | } 214 | 215 | def mouseCoord = Vec(Mouse.getX, Mouse.getY) 216 | 217 | def isMouseMoved = Mouse.getDX != 0 || Mouse.getDY != 0 218 | 219 | private def mouseButton(button_code: Int, repeat_time: => Long = 0, onButtonDown: Vec => Any, onButtonUp: Vec => Any = Vec => {}) = { 220 | mouse_button_events(button_code) = SingleMouseButtonEvent(button_code, () => repeat_time, onButtonDown, onButtonUp) 221 | control_deletion_operations.addOp(() => mouse_button_events -= button_code, 0) 222 | } 223 | 224 | def leftMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 225 | mouseButton(0, repeat_time, mouse_coord => if (!on_pause) onBtnDown(mouse_coord), mouse_coord => if (!on_pause) onBtnUp(mouse_coord)) 226 | } 227 | 228 | def leftMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 229 | mouseButton(0, repeat_time, onBtnDown, onBtnUp) 230 | } 231 | 232 | def leftMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 233 | mouseButton(0, repeat_time, mouse_coord => if (on_pause) onBtnDown(mouse_coord), mouse_coord => if (on_pause) onBtnUp(mouse_coord)) 234 | } 235 | 236 | def rightMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 237 | mouseButton(1, repeat_time, mouse_coord => if (!on_pause) onBtnDown(mouse_coord), mouse_coord => if (!on_pause) onBtnUp(mouse_coord)) 238 | } 239 | 240 | def rightMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 241 | mouseButton(1, repeat_time, onBtnDown, onBtnUp) 242 | } 243 | 244 | def rightMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = { 245 | mouseButton(1, repeat_time, mouse_coord => if (on_pause) onBtnDown(mouse_coord), mouse_coord => if (on_pause) onBtnUp(mouse_coord)) 246 | } 247 | 248 | def mouseMotion(onMotion: Vec => Any) = { 249 | on_mouse_motion = mouse_coord => if (!on_pause) onMotion(mouse_coord) 250 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0) 251 | } 252 | 253 | def mouseMotionIgnorePause(onMotion: Vec => Any) = { 254 | on_mouse_motion = onMotion 255 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0) 256 | } 257 | 258 | def mouseMotionOnPause(onMotion: Vec => Any) = { 259 | on_mouse_motion = mouse_coord => if (on_pause) onMotion(mouse_coord) 260 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0) 261 | } 262 | 263 | private def mouseDrag(button_code: Int, onDrag: Vec => Any) = { 264 | on_mouse_drag_motion(button_code) = onDrag 265 | control_deletion_operations.addOp(() => on_mouse_drag_motion -= button_code, 0) 266 | } 267 | 268 | def leftMouseDrag(onDrag: Vec => Any) = { 269 | mouseDrag(0, mouse_coord => if (!on_pause) onDrag(mouse_coord)) 270 | } 271 | 272 | def leftMouseDragIgnorePause(onDrag: Vec => Any) = { 273 | mouseDrag(0, onDrag) 274 | } 275 | 276 | def leftMouseDragOnPause(onDrag: Vec => Any) = { 277 | mouseDrag(0, mouse_coord => if (on_pause) onDrag(mouse_coord)) 278 | } 279 | 280 | def rightMouseDrag(onDrag: Vec => Any) = { 281 | mouseDrag(1, mouse_coord => if (!on_pause) onDrag(mouse_coord)) 282 | } 283 | 284 | def rightMouseDragIgnorePause(onDrag: Vec => Any) = { 285 | mouseDrag(1, onDrag) 286 | } 287 | 288 | def rightMouseDragOnPause(onDrag: Vec => Any) = { 289 | mouseDrag(1, mouse_coord => if (on_pause) onDrag(mouse_coord)) 290 | } 291 | 292 | def mouseWheelUp(onWheelUp: Vec => Any) = { 293 | on_mouse_wheel_up = mouse_coord => if (!on_pause) onWheelUp(mouse_coord) 294 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0) 295 | } 296 | 297 | def mouseWheelUpIgnorePause(onWheelUp: Vec => Any) = { 298 | on_mouse_wheel_up = onWheelUp 299 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0) 300 | } 301 | 302 | def mouseWheelUpOnPause(onWheelUp: Vec => Any) = { 303 | on_mouse_wheel_up = mouse_coord => if (on_pause) onWheelUp(mouse_coord) 304 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0) 305 | } 306 | 307 | def mouseWheelDown(onWheelDown: Vec => Any) = { 308 | on_mouse_wheel_down = mouse_coord => if (!on_pause) onWheelDown(mouse_coord) 309 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0) 310 | } 311 | 312 | def mouseWheelDownIgnorePause(onWheelDown: Vec => Any) = { 313 | on_mouse_wheel_down = onWheelDown 314 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0) 315 | } 316 | 317 | def mouseWheelDownOnPause(onWheelDown: Vec => Any) = { 318 | on_mouse_wheel_down = mouse_coord => if (on_pause) onWheelDown(mouse_coord) 319 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0) 320 | } 321 | 322 | //private val log = MySimpleLogger(this.getClass.getName) 323 | 324 | def checkControls() { 325 | val inputs_history = ControllerActorSystem.getInputsHistoryAndReset 326 | for { 327 | /*(*/ InputsHistoryMoment(moment, keys, mouse_coord, is_mouse_moved, dwheel, mouse_buttons) /*, idx)*/ <- inputs_history /*.zipWithIndex*/ 328 | if is_running && Scage.isAppRunning 329 | } { 330 | var any_key_pressed = false 331 | 332 | for { 333 | (key, key_data) <- keyboard_key_events 334 | SingleKeyEvent(_, repeat_time_func, onKeyDown, onKeyUp) = key_data 335 | key_press@KeyPress(key_code, was_pressed, pressed_start_time, last_pressed_time) <- innerKeyPress(key) 336 | if moment > maxLastPressedTime 337 | is_key_pressed <- keys.get(key) 338 | } { 339 | if (is_key_pressed) { 340 | any_key_pressed = true 341 | val repeat_time = repeat_time_func() 342 | val is_repeatable = repeat_time > 0 343 | if (!key_press.was_pressed || (is_repeatable && moment - key_press.lastPressedTime > repeat_time)) { 344 | //log.info(s"$unit_name $key onKeyDown: $idx ${inputs_history.map(_.keys(key))} $key_press") 345 | if (!key_press.was_pressed) key_press.pressed_start_time = moment 346 | key_press.was_pressed = true 347 | key_press.updateLastPressedTime(moment) 348 | onKeyDown() 349 | } 350 | } else if (key_press.was_pressed) { 351 | key_press.was_pressed = false 352 | //log.info(s"$unit_name $key onKeyUp: $idx ${inputs_history.map(_.keys(key))} $key_press") 353 | onKeyUp() 354 | } 355 | } 356 | 357 | if (any_key_pressed) anykey() 358 | if (is_mouse_moved) on_mouse_motion(mouse_coord) 359 | 360 | for { 361 | (button, button_data) <- mouse_button_events 362 | SingleMouseButtonEvent(_, repeat_time_func, onButtonDown, onButtonUp) = button_data 363 | mouse_button_press@MouseButtonPress(_, was_pressed, _, last_pressed_time) <- innerMouseButtonPress(button) 364 | if moment > maxLastPressedTime 365 | is_button_pressed <- mouse_buttons.get(button) 366 | } { 367 | if (is_button_pressed) { 368 | val repeat_time = repeat_time_func() 369 | val is_repeatable = repeat_time > 0 370 | if (!was_pressed || (is_repeatable && moment - last_pressed_time > repeat_time)) { 371 | //log.info(s"$unit_name leftMouse onBtnDown: $idx ${inputs_history.map(_.mouse_buttons(0))} $mouse_button_press") 372 | if (!mouse_button_press.was_pressed) mouse_button_press.pressed_start_time = moment 373 | mouse_button_press.was_pressed = true 374 | mouse_button_press.updateLastPressedTime(moment) 375 | onButtonDown(mouse_coord) 376 | } 377 | } else if (was_pressed) { 378 | mouse_button_press.was_pressed = false 379 | //log.info(s"$unit_name leftMouse onBtnUp: $idx ${inputs_history.map(_.mouse_buttons(0))} $mouse_button_press") 380 | onButtonUp(mouse_coord) 381 | } 382 | } 383 | 384 | if (is_mouse_moved) { 385 | for { 386 | (button, onDragMotion) <- on_mouse_drag_motion 387 | if mouse_buttons.getOrElse(button, false) 388 | } onDragMotion(mouse_coord) 389 | } 390 | 391 | dwheel match { 392 | case x if x > 0 => on_mouse_wheel_up(mouse_coord) 393 | case x if x < 0 => on_mouse_wheel_down(mouse_coord) 394 | case _ => 395 | } 396 | } 397 | } 398 | } 399 | --------------------------------------------------------------------------------