Separated langpack, concept art for inventory UI

Former-commit-id: 7a98df93b4ef50b47283abcd99576d6fbefc9cc5
Former-commit-id: db6e34417ccf84e59ba68547f30459cb4b188eb7
This commit is contained in:
Song Minjae
2016-07-04 23:08:16 +09:00
parent 2ed435165a
commit 2e46df67a8
91 changed files with 70088 additions and 720 deletions

View File

@@ -2,6 +2,7 @@ package net.torvald
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import java.io.File
import java.io.IOException
import java.nio.file.FileSystems
@@ -27,6 +28,17 @@ object JsonFetcher {
return jsonObj
}
@Throws(IOException::class)
fun readJson(jsonFile: File): JsonObject {
jsonString = StringBuffer() // reset buffer every time it called
readJsonFileAsString(jsonFile.canonicalPath)
val jsonParser = JsonParser()
val jsonObj = jsonParser.parse(jsonString!!.toString()).asJsonObject
return jsonObj
}
@Throws(IOException::class)
private fun readJsonFileAsString(path: String) {
Files.lines(FileSystems.getDefault().getPath(path)).forEach(

View File

@@ -1,3 +1,10 @@
## Atmosphere ##
* Serene (not all serene of course)
* There's living village, abandoned/crumbling settlement and lone shacks on the mountainside
* History, much like one in the _Dwarf Fortress_, decaying of the settlement is simulated by the very method, but 1-2 will be forced. The amount is dependent on the size of the map
* Reference: _Legend of Zelda: Breath of the Wild Trailer_ (the trailer came much after this game but the video will help you to get a grip)
## Colour overlay ##
Colour overlay is set to 6 500 K as untouched. The value must be

View File

@@ -0,0 +1,48 @@
The game has elements that are:
- Actors
- Controller
- AI
- JInput (Provided by LWJGL)
- Factioning
- Scheduler
- Physics solver (simple, AABB-oriented)
- _TODO: sprite assembler (character--avatar--maker)_
- Imagefont
- Font drawer
- Hangul character assembler (aka JOHAB)
- Sprite animator (really simple one)
- De/serialiser
- Concurrency helper (really simple one)
- Tiles
- Tile property database
- Items
- Item property database
- _TODO: Material system_
- Map drawer
- Map camera
- Map drawer
- Lightmap renderer
- Map generator
- Utilises Joise Modular Noise Generator
- Additional noise filters
- Real estate
- The registry (a book that records the current owner of tiles on the map. Owner can be a single NPC or faction)
- Utility
- Internationalisation
- Language pack
The elements are connected like:
- Actors
- Controller ← AI, Player Input
- AI ← Factioning, Scheduler
- class "Actor"
- class "ActorWithBody"
with properties like (AIControlled, Factionable, Luminous, Visible, etc.)
- class "NPCIntelligentBase"
- (NPCs that has AI and can be interacted)
- (fixtures, player)
- (any of the invisible actors)

View File

@@ -116,4 +116,10 @@ see SAVE_FORMAT.md
## Ownership of lands ##
* Codex of Real Estate → assign owner to the tiles → copy the information to the NPC instance
## Health and magic point ##
* Works like Ampere in electronics.
- Health: Regen per time
- Magic: Out power per time

View File

@@ -0,0 +1,19 @@
## Minimum requirements ##
* Any CPU with 1.8 GHz in speed
* GPU that supports OpenGL 2.0, is capable of 4K texture (8K for Chinese/Japanese language)
* 2 GB of RAM
* 2 GB of free disk space
## Recommended requirements ##
* Intel i5 with 2.4+ GHz in speed (or any CPU with 4 or more threads, 2.4+ GHz in speed)
* GPU that supports OpenGL 2.0, is capable of 8K texture
* 4 GB of RAM
* 5 GB of free disk space
## Tested environment ##
(to devs: please extend this list with your test results!)
* MacBookPro9,2 (MacBook Pro 13 inch mid-2012)

View File

@@ -9,7 +9,7 @@ import org.newdawn.slick.state.StateBasedGame
/**
* Created by minjaesong on 16-06-28.
*/
class StateFontPrinter : BasicGameState() {
class StateFontTester : BasicGameState() {
val textToPrint = "Font printer 서체 인쇄기"
lateinit var canvas: Graphics

View File

@@ -80,32 +80,32 @@ constructor() : BasicGameState() {
val auth = Authenticator()
private var update_delta: Int = 0
val KEY_LIGHTMAP_RENDER = Key.F7
val KEY_LIGHTMAP_SMOOTH = Key.F8
var DELTA_T: Int = 0
var UPDATE_DELTA: Int = 0
@Throws(SlickException::class)
override fun init(gameContainer: GameContainer, stateBasedGame: StateBasedGame) {
KeyMap.build()
// load necessary shaders
shader12BitCol = Shader.makeShader("./res/4096.vrt", "./res/4096.frg")
shaderBlurH = Shader.makeShader("./res/blurH.vrt", "./res/blur.frg")
shaderBlurV = Shader.makeShader("./res/blurV.vrt", "./res/blur.frg")
// init skybox
GRADIENT_IMAGE = Image("res/graphics/colourmap/sky_colour.png")
skyBox = Rectangle(0f, 0f, Terrarum.WIDTH.toFloat(), Terrarum.HEIGHT.toFloat())
// init map as chosen size
map = GameMap(8192, 2048)
// generate terrain for the map
MapGenerator.attachMap(map)
MapGenerator.SEED = 0x51621D2
//mapgenerator.setSeed(new HQRNG().nextLong());
MapGenerator.generateMap()
RoguelikeRandomiser.seed = 0x540198
//RoguelikeRandomiser.setSeed(new HQRNG().nextLong());
@@ -116,27 +116,27 @@ constructor() : BasicGameState() {
//player.setNoClip(true);
addActor(player)
// init console window
consoleHandler = UIHandler(ConsoleWindow())
consoleHandler.setPosition(0, 0)
// init debug window
debugWindow = UIHandler(BasicDebugInfoWindow())
debugWindow.setPosition(0, 0)
// init notifier
notifier = UIHandler(Notification())
notifier.setPosition(
(Terrarum.WIDTH - notifier.UI.width) / 2, Terrarum.HEIGHT - notifier.UI.height)
notifier.setVisibility(true)
if (Terrarum.getConfigBoolean("smoothlighting") == true)
KeyToggler.forceSet(KEY_LIGHTMAP_SMOOTH, true)
else
KeyToggler.forceSet(KEY_LIGHTMAP_SMOOTH, false)
// set smooth lighting as in config
KeyToggler.forceSet(KEY_LIGHTMAP_SMOOTH, Terrarum.getConfigBoolean("smoothlighting"))
}
override fun update(gc: GameContainer, sbg: StateBasedGame, delta: Int) {
DELTA_T = delta
UPDATE_DELTA = delta
update_delta = delta
setAppTitle()
map.updateWorldTime(delta)
@@ -157,10 +157,9 @@ constructor() : BasicGameState() {
updateActors(gc, delta)
// TODO thread pool
// TODO thread pool(?)
CollisionSolver.process()
// TODO thread pool
uiContainer.forEach { ui -> ui.update(gc, delta) }
consoleHandler.update(gc, delta)
debugWindow.update(gc, delta)
@@ -195,12 +194,14 @@ constructor() : BasicGameState() {
MapCamera.renderBehind(gc, g)
// draw actors
actorContainer.forEach { actor ->
if (actor is Visible && actor.inScreen() && actor !is Player) { // if visible and within screen
actor.drawBody(gc, g)
run {
actorContainer.forEach { actor ->
if (actor is Visible && actor.inScreen() && actor !is Player) { // if visible and within screen
actor.drawBody(gc, g)
}
}
player.drawBody(gc, g)
}
player.drawBody(gc, g)
LightmapRenderer.renderLightMap()
@@ -208,21 +209,21 @@ constructor() : BasicGameState() {
MapDrawer.render(gc, g)
setBlendMul()
MapDrawer.drawEnvOverlay(g)
if (!KeyToggler.isOn(KEY_LIGHTMAP_RENDER)) setBlendMul() else setBlendNormal()
LightmapRenderer.draw(g)
setBlendNormal()
// draw actor glows
actorContainer.forEach { actor ->
if (actor is Visible && actor.inScreen() && actor !is Player) { // if visible and within screen
actor.drawGlow(gc, g)
run {
actorContainer.forEach { actor ->
if (actor is Visible && actor.inScreen() && actor !is Player) { // if visible and within screen
actor.drawGlow(gc, g)
}
}
player.drawGlow(gc, g)
}
player.drawGlow(gc, g)
// draw reference ID if debugWindow is open
if (debugWindow.visible) {
@@ -249,10 +250,12 @@ constructor() : BasicGameState() {
}
// draw UIs
uiContainer.forEach { ui -> ui.render(gc, g) }
debugWindow.render(gc, g)
consoleHandler.render(gc, g)
notifier.render(gc, g)
run {
uiContainer.forEach { ui -> ui.render(gc, g) }
debugWindow.render(gc, g)
consoleHandler.render(gc, g)
notifier.render(gc, g)
}
}
private fun getGradientColour(row: Int, phase: Int) = GRADIENT_IMAGE!!.getColor(phase, row)
@@ -327,46 +330,52 @@ constructor() : BasicGameState() {
/** Send message to notifier UI and toggle the UI as opened. */
fun sendNotification(msg: Array<String>) {
(notifier.UI as Notification).sendNotification(Terrarum.appgc, update_delta, msg)
(notifier.UI as Notification).sendNotification(Terrarum.appgc, UPDATE_DELTA, msg)
notifier.setAsOpening()
}
fun wakeDormantActors() {
// determine whether the dormant actor should be re-activated
var actorContainerSize = actorContainerInactive.size
var i = 0
while (i < actorContainerSize) { // loop thru actorContainerInactive
while (i < actorContainerSize) { // loop through actorContainerInactive
val actor = actorContainerInactive[i]
val actorIndex = i
if (actor is Visible && actor.inUpdateRange()) {
addActor(actor) // duplicates are checked here
actorContainerInactive.removeAt(actorIndex)
actorContainerSize -= 1
i-- // array removed 1 elem, so also decrement counter by 1
i-- // array removed 1 elem, so we also decrement counter by 1
}
i++
}
}
/**
* determine whether the actor should be active or dormant by its distance from the player.
* If the actor must be dormant, the target actor will be put to the list specifically for them.
* if the actor is not to be dormant, it will be just ignored.
*/
fun InactivateDistantActors() {
var actorContainerSize = actorContainer.size
var i = 0
// determine whether the actor should be active or dormant by its distance from the player.
// If the actor must be dormant, the target actor will be put to the list specifically for them.
// if the actor is not to be dormant, update it
while (i < actorContainerSize) { // loop through the actorContainer
while (i < actorContainerSize) { // loop through actorContainer
val actor = actorContainer[i]
val actorIndex = i
if (actor is Visible && !actor.inUpdateRange()) {
actorContainerInactive.add(actor) // naïve add; duplicates are checked when the actor is re-activated
actorContainer.removeAt(actorIndex)
actorContainerSize -= 1
i-- // array removed 1 elem, so also decrement counter by 1
i-- // array removed 1 elem, so we also decrement counter by 1
}
i++
}
}
/**
* Update actors concurrently.
*
* NOTE: concurrency for actor updating is currently disabled because of it's poor performance
*/
fun updateActors(gc: GameContainer, delta: Int) {
if (false) { // don't multithread this for now, it's SLOWER //if (Terrarum.MULTITHREAD) {
val actors = actorContainer.size.toFloat()
@@ -394,7 +403,7 @@ constructor() : BasicGameState() {
get() = getGradientColour(2).getRGB24().rgb24ExpandToRgb30()
fun globalLightByTime(t: Int): Int = getGradientColourByTime(2, t).getRGB24().rgb24ExpandToRgb30()
fun Color.getRGB24(): Int = (this.redByte shl 16) or (this.greenByte shl 8) or (this.blueByte)
fun Color.getRGB24(): Int = this.redByte.shl(16) or this.greenByte.shl(8) or this.blueByte
/** Remap 8-bit value (0.0-1.0) to 10-bit value (0.0-4.0) by prepending two bits of zero for each R, G and B. */
fun Int.rgb24ExpandToRgb30(): Int = (this and 0xff) or
(this and 0xff00).ushr(8).shl(10) or
@@ -402,11 +411,13 @@ constructor() : BasicGameState() {
fun Double.sqr() = this * this
fun Int.sqr() = this * this
private fun distToActorSqr(a: Visible, p: Player): Float =
(a.hitbox.centeredX - p.hitbox.centeredX).sqr().toFloat() + (a.hitbox.centeredY - p.hitbox.centeredY).sqr().toFloat()
private fun distToActorSqr(a: Visible, p: Player): Double =
(a.hitbox.centeredX - p.hitbox.centeredX).sqr() + (a.hitbox.centeredY - p.hitbox.centeredY).sqr()
/** whether the actor is within screen */
private fun Visible.inScreen() = distToActorSqr(this, player) <=
(Terrarum.WIDTH.plus(this.hitbox.width.div(2)).times(1 / Terrarum.game.screenZoom).sqr() +
Terrarum.HEIGHT.plus(this.hitbox.height.div(2)).times(1 / Terrarum.game.screenZoom).sqr())
/** whether the actor is within update range */
private fun Visible.inUpdateRange() = distToActorSqr(this, player) <= ACTOR_UPDATE_RANGE.sqr()
/**
* actorContainer extensions
@@ -459,10 +470,9 @@ constructor() : BasicGameState() {
}
private fun insertionSortLastElem(arr: ArrayList<Actor>) {
var x: Actor
var j: Int
var index: Int = arr.size - 1
x = arr[index]
val index: Int = arr.size - 1
val x = arr[index]
j = index - 1
while (j > 0 && arr[j] > x) {
arr[j + 1] = arr[j]

View File

@@ -34,11 +34,13 @@ constructor(gamename: String) : StateBasedGame(gamename) {
if (!readFromDisk) readConfigJson()
// get locale from config
gameLocale = gameConfig.getAsString("language") ?: sysLang
val gameLocaleFromConfig = gameConfig.getAsString("language") ?: sysLang
// if bad game locale were set, use system locale
if (gameLocale.length < 4)
if (gameLocaleFromConfig.length < 2)
gameLocale = sysLang
else
gameLocale = gameLocaleFromConfig
println("[terrarum] Locale: " + gameLocale)
}
@@ -98,7 +100,14 @@ constructor(gamename: String) : StateBasedGame(gamename) {
lateinit var defaultSaveDir: String
private set
var gameLocale = "" // locale override
private val localeSimple = arrayOf("de", "en", "es", "it")
var gameLocale = "en" // locale override
set(value) {
if (localeSimple.contains(value.substring(0..1)))
field = value.substring(0..1)
else
field = value
}
lateinit var gameFont: Font
private set
@@ -145,11 +154,14 @@ constructor(gamename: String) : StateBasedGame(gamename) {
appgc.setTargetFrameRate(TARGET_INTERNAL_FPS)
appgc.setVSync(VSYNC)
appgc.setMaximumLogicUpdateInterval(1000 / TARGET_INTERNAL_FPS)
appgc.setMinimumLogicUpdateInterval(1000 / TARGET_INTERNAL_FPS - 1)
appgc.setMaximumLogicUpdateInterval(1000 / TARGET_INTERNAL_FPS) // 10 ms
appgc.setMinimumLogicUpdateInterval(1000 / TARGET_INTERNAL_FPS - 1) // 9 ms
appgc.setShowFPS(false)
// game will run normally even if it is not focused
appgc.setUpdateOnlyWhenVisible(false)
appgc.alwaysRender = true
appgc.start()
}
@@ -165,7 +177,6 @@ constructor(gamename: String) : StateBasedGame(gamename) {
fileHandler.formatter = formatter
//logger.info()
println("The game has been crashed!")
println("Crash log were saved to $filepath.")
println("================================================================================")

View File

@@ -41,7 +41,8 @@ object CommandDict {
// Test codes
Pair("bulletintest", SetBulletin()),
Pair("gsontest", GsonTest())
Pair("gsontest", GsonTest()),
Pair("tips", PrintRandomTips())
)
fun getCommand(commandName: String): ConsoleCommand {

View File

@@ -15,7 +15,16 @@ import java.util.regex.Pattern
*/
object CommandInterpreter {
private val commandsNoAuth = arrayOf("auth", "qqq", "zoom", "setlocale", "getlocale", "help", "version")
private val commandsNoAuth = arrayOf(
"auth",
"qqq",
"zoom",
"setlocale",
"getlocale",
"help",
"version",
"tips"
)
private val ccW = GameFontBase.colToCode["w"]
private val ccG = GameFontBase.colToCode["g"]

View File

@@ -16,7 +16,16 @@ internal class Echo : ConsoleCommand {
}
fun execute(single_line: String) {
(Terrarum.game.consoleHandler.UI as ConsoleWindow).sendMessage(single_line)
val sb = StringBuilder()
for (ch in single_line) {
if (ch == '\n') {
(Terrarum.game.consoleHandler.UI as ConsoleWindow).sendMessage(sb.toString())
sb.delete(0, sb.length - 1)
}
else
sb.append(ch)
}
(Terrarum.game.consoleHandler.UI as ConsoleWindow).sendMessage(sb.toString())
}
override fun printUsage() {

View File

@@ -10,9 +10,9 @@ class GetLocale : ConsoleCommand {
override fun execute(args: Array<String>) {
Echo().execute(
"Locale: "
+ Lang.get("LANGUAGE_THIS")
+ Lang["MENU_LANGUAGE_THIS"]
+ " ("
+ Lang.get("LANGUAGE_EN")
+ Lang["MENU_LANGUAGE_THIS_EN"]
+ ")")
}

View File

@@ -0,0 +1,17 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.langpack.Lang
import java.util.*
/**
* Created by minjaesong on 16-07-04.
*/
class PrintRandomTips : ConsoleCommand {
override fun execute(args: Array<String>) {
Echo().execute(Lang["GAME_TIPS_${Random().nextInt(Lang.TIPS_COUNT) + 1}"])
}
override fun printUsage() {
Echo().execute("Prints random tips for game.")
}
}

View File

@@ -29,8 +29,7 @@ class SetLocale : ConsoleCommand {
val echo = Echo()
echo.execute("Locales:")
val record = Lang.getRecord("LANGUAGE_ID")
record.forEach { field -> echo.execute("] " + field) }
Lang.languageList.forEach { echo.execute("--> $it") }
}
else {
printUsage()

View File

@@ -1,13 +1,16 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.langpack.Lang
/**
* Created by minjaesong on 16-04-23.
*/
class Version : ConsoleCommand {
override fun execute(args: Array<String>) {
Echo().execute("${Terrarum.NAME} ${Terrarum.VERSION_STRING}")
val echo = Echo()
echo.execute("${Terrarum.NAME} ${Terrarum.VERSION_STRING}")
echo.execute("Polyglot language pack version ${Lang.POLYGLOT_VERSION}")
}
override fun printUsage() {

View File

@@ -118,7 +118,7 @@ open class ActorWithBody : Actor(), Visible {
var isNoCollideWorld = false
var isNoSubjectToFluidResistance = false
/** Time-freezing. The actor won't even budge but the velocity will accumulate
* EXCEPT FOR friction, gravity and buoyancy SHOULD NOT.
* EXCEPT FOR friction, gravity and buoyancy.
*
* (this would give something to do to the player otherwise it would be dull to travel far,
* think of a grass cutting on the Zelda games. It would also make a great puzzle to solve.
@@ -175,7 +175,7 @@ open class ActorWithBody : Actor(), Visible {
@Transient private val CCD_TRY_MAX = 12800
// just some trivial magic numbers
@Transient private val A_PIXEL = 1.4
@Transient private val A_PIXEL = 1.0
@Transient private val COLLIDING_TOP = 0
@Transient private val COLLIDING_RIGHT = 1
@Transient private val COLLIDING_BOTTOM = 2
@@ -234,7 +234,7 @@ open class ActorWithBody : Actor(), Visible {
baseHitboxH * scale)
}
override fun run() = update(Terrarum.appgc, Terrarum.game.DELTA_T)
override fun run() = update(Terrarum.appgc, Terrarum.game.UPDATE_DELTA)
/**
* Add vector value to the velocity, in the time unit of single frame.
@@ -274,19 +274,21 @@ open class ActorWithBody : Actor(), Visible {
/**
* Actual physics thing (altering velocity) starts from here
*/
// Actors are subject to the gravity and the buoyancy if they are not levitating
if (!isNoSubjectToGrav) {
applyGravitation()
//applyBuoyancy()
}
// hard limit velocity
veloX = veloX.bipolarClamp(VELO_HARD_LIMIT)
veloY = veloY.bipolarClamp(VELO_HARD_LIMIT)
applyControllerMovement()
moveDelta.set(velocity + Vector2(walkX, walkY))
// applyBuoyancy()
if (!isChronostasis) {
// Actors are subject to the gravity and the buoyancy if they are not levitating
if (!isNoSubjectToGrav) {
applyGravitation()
}
// hard limit velocity
veloX = veloX.bipolarClamp(VELO_HARD_LIMIT)
veloY = veloY.bipolarClamp(VELO_HARD_LIMIT)
// Set 'next' position (hitbox) from canonical and walking velocity
setNewNextHitbox()
@@ -309,8 +311,25 @@ open class ActorWithBody : Actor(), Visible {
clampHitbox()
}
walledLeft = isColliding(hitbox, COLLIDING_LEFT)
walledRight = isColliding(hitbox, COLLIDING_RIGHT)
// useless
walledLeft = false//isColliding(hitbox, COLLIDING_LEFT)
walledRight = false//isColliding(hitbox, COLLIDING_RIGHT)
}
}
private fun applyControllerMovement() {
// decide whether ignore walkX
if (!(isCollidingSide(hitbox, COLLIDING_LEFT) && walkX < 0)
|| !(isCollidingSide(hitbox, COLLIDING_RIGHT) && walkX > 0)
) {
moveDelta.x = veloX + walkX
}
// decide whether ignore walkY
if (!(isCollidingSide(hitbox, COLLIDING_TOP) && walkY < 0)
|| !(isCollidingSide(hitbox, COLLIDING_BOTTOM) && walkY > 0)
) {
moveDelta.y = veloY + walkY
}
}
@@ -320,7 +339,7 @@ open class ActorWithBody : Actor(), Visible {
* Apply only if not grounded; normal force is precessed separately.
*/
private fun applyGravitation() {
if (!grounded) {//(!isColliding(COLLIDING_BOTTOM)) { // or !grounded
if (!isTouchingSide(hitbox, COLLIDING_BOTTOM)) {//(!isColliding(COLLIDING_BOTTOM)) { // or !grounded
/**
* weight; gravitational force in action
* W = mass * G (9.8 [m/s^2])
@@ -346,7 +365,7 @@ open class ActorWithBody : Actor(), Visible {
if (!isNoCollideWorld) {
// axis Y
if (moveDelta.y >= 0.0) { // was moving downward?
if (ccdCollided) { // actor hit something on its bottom
if (isTouchingSide(nextHitbox, COLLIDING_BOTTOM)) { // actor hit something on its bottom
hitAndReflectY()
grounded = true
}
@@ -356,14 +375,15 @@ open class ActorWithBody : Actor(), Visible {
}
else if (moveDelta.y < 0.0) { // or was moving upward?
grounded = false
if (ccdCollided) { // actor hit something on its top
if (isTouchingSide(nextHitbox, COLLIDING_TOP)) { // actor hit something on its top
hitAndReflectY()
}
else { // the actor is not grounded at all
}
}
// axis X
if (ccdCollided && moveDelta.x != 0.0) { // check right and left
if (isTouchingSide(nextHitbox, COLLIDING_LEFT) && isTouchingSide(nextHitbox, COLLIDING_RIGHT)
&& moveDelta.x != 0.0) { // check right and left
// the actor is hitting the wall
hitAndReflectX()
}
@@ -376,23 +396,26 @@ open class ActorWithBody : Actor(), Visible {
private fun displaceByCCD() {
if (!isNoCollideWorld){
// do some CCD between hitbox and nextHitbox
val ccdBox = hitbox.clone()
val ccdDelta = hitbox.toVector() to nextHitbox.toVector()
val ccdStep = Math.max(ccdDelta.x, ccdDelta.y)
val ccdBox = nextHitbox.clone().snapToPixel()
val ccdDelta = nextHitbox.toVector() to hitbox.toVector()
val deltaMax = Math.max(ccdDelta.x.abs(), ccdDelta.y.abs())
// set ccdDelta so that CCD move a pixel per round
if (deltaMax > 1.0)
ccdDelta *= (1.0 / deltaMax)
for (step in 0..ccdStep.floorInt()) {
ccdBox.translate(ccdDelta * (step.toDouble() / ccdStep))
//println("deltaMax: $deltaMax")
//println("ccdDelta: $ccdDelta")
println(ccdDelta * (step.toDouble() / ccdStep))
println(ccdBox)
while (!ccdDelta.isZero && isColliding(ccdBox)) {
nextHitbox.translate(ccdDelta)
ccdCollided = true
if (isColliding(ccdBox)) {
ccdCollided = true
break
}
ccdBox.reassign(nextHitbox).snapToPixel()
}
nextHitbox.reassign(ccdBox)
nextHitbox.snapToPixel()
//println("ccdCollided: $ccdCollided")
}
else {
ccdCollided = false
@@ -470,10 +493,99 @@ open class ActorWithBody : Actor(), Visible {
}
}
val txStart = x1.div(TSIZE).floorInt()
val txEnd = x2.div(TSIZE).floorInt()
val tyStart = y1.div(TSIZE).floorInt()
val tyEnd = y2.div(TSIZE).floorInt()
val txStart = x1.div(TSIZE).roundInt()
val txEnd = x2.div(TSIZE).roundInt()
val tyStart = y1.div(TSIZE).roundInt()
val tyEnd = y2.div(TSIZE).roundInt()
for (y in tyStart..tyEnd) {
for (x in txStart..txEnd) {
val tile = map.getTileFromTerrain(x, y)
if (TilePropCodex.getProp(tile).isSolid)
return true
}
}
return false
}
private fun isTouchingSide(hitbox: Hitbox, option: Int): Boolean {
val x1: Double; val x2: Double; val y1: Double; val y2: Double
if (option == COLLIDING_TOP) {
x1 = hitbox.posX
x2 = hitbox.endPointX
y1 = hitbox.posY - A_PIXEL
y2 = y1
}
else if (option == COLLIDING_BOTTOM) {
x1 = hitbox.posX
x2 = hitbox.endPointX
y1 = hitbox.endPointY + A_PIXEL
y2 = y1
}
else if (option == COLLIDING_LEFT) {
x1 = hitbox.posX - A_PIXEL
x2 = x1
y1 = hitbox.posY
y2 = hitbox.endPointY
}
else if (option == COLLIDING_RIGHT) {
x1 = hitbox.endPointX + A_PIXEL
x2 = x1
y1 = hitbox.posY
y2 = hitbox.endPointY
}
else throw IllegalArgumentException()
val txStart = x1.div(TSIZE).roundInt()
val txEnd = x2.div(TSIZE).roundInt()
val tyStart = y1.div(TSIZE).roundInt()
val tyEnd = y2.div(TSIZE).roundInt()
for (y in tyStart..tyEnd) {
for (x in txStart..txEnd) {
val tile = map.getTileFromTerrain(x, y)
if (TilePropCodex.getProp(tile).isSolid)
return true
}
}
return false
}
private fun isCollidingSide(hitbox: Hitbox, option: Int): Boolean {
val x1: Double; val x2: Double; val y1: Double; val y2: Double
if (option == COLLIDING_TOP) {
x1 = hitbox.posX
x2 = hitbox.endPointX
y1 = hitbox.posY
y2 = y1
}
else if (option == COLLIDING_BOTTOM) {
x1 = hitbox.posX
x2 = hitbox.endPointX
y1 = hitbox.endPointY
y2 = y1
}
else if (option == COLLIDING_LEFT) {
x1 = hitbox.posX
x2 = x1
y1 = hitbox.posY
y2 = hitbox.endPointY
}
else if (option == COLLIDING_RIGHT) {
x1 = hitbox.endPointX
x2 = x1
y1 = hitbox.posY
y2 = hitbox.endPointY
}
else throw IllegalArgumentException()
val txStart = x1.div(TSIZE).roundInt()
val txEnd = x2.div(TSIZE).roundInt()
val tyStart = y1.div(TSIZE).roundInt()
val tyEnd = y2.div(TSIZE).roundInt()
for (y in tyStart..tyEnd) {
for (x in txStart..txEnd) {
@@ -792,7 +904,7 @@ open class ActorWithBody : Actor(), Visible {
fun Double.sqr() = this * this
fun Int.abs() = if (this < 0) -this else this
fun Double.bipolarClamp(limit: Double) =
if (this > 0 && this > limit) limit
if (this > 0 && this > limit) limit
else if (this < 0 && this < -limit) -limit
else this
@@ -800,11 +912,12 @@ open class ActorWithBody : Actor(), Visible {
// errors
if (baseHitboxW == 0 || baseHitboxH == 0)
throw RuntimeException("Hitbox dimension was not set.")
if (sprite == null && isVisible)
throw RuntimeException("Actor ${this.javaClass.canonicalName} is visible but the sprite was not set.")
// warnings
if (!isVisible && sprite != null)
println("[ActorWithBody] Caution: actor ${this.javaClass.canonicalName} is invisible but the sprite was given.")
if (sprite == null && isVisible)
println("[ActorWithBody] Caution: actor ${this.javaClass.simpleName} is visible but the sprite was not set.")
else if (sprite != null && !isVisible)
println("[ActorWithBody] Caution: actor ${this.javaClass.simpleName} is invisible but the sprite was given.")
assertPrinted = true
}

View File

@@ -17,6 +17,8 @@ class Hitbox(x1: Double, y1: Double, width: Double, height: Double) {
var height: Double = 0.0
private set
val HALF_PIXEL = 0.5
init {
hitboxStart = Point2d(x1, y1)
hitboxEnd = Point2d(x1 + width, y1 + height)
@@ -54,62 +56,56 @@ class Hitbox(x1: Double, y1: Double, width: Double, height: Double) {
* @param width
* @param height
*/
fun set(x1: Double, y1: Double, width: Double, height: Double) {
fun set(x1: Double, y1: Double, width: Double, height: Double): Hitbox {
hitboxStart = Point2d(x1, y1)
hitboxEnd = Point2d(x1 + width, y1 + height)
this.width = width
this.height = height
return this
}
fun reassign(other: Hitbox) = set(other.posX, other.posY, other.width, other.height)
fun reassign(other: Hitbox) {
set(other.posX, other.posY, other.width, other.height)
}
fun translate(x: Double, y: Double) = setPosition(posX + x, posY + y)
fun translate(vec: Vector2) = translate(vec.x, vec.y)
fun translate(x: Double, y: Double) {
setPosition(posX + x, posY + y)
}
fun translate(vec: Vector2) {
translate(vec.x, vec.y)
}
fun setPosition(x1: Double, y1: Double) {
fun setPosition(x1: Double, y1: Double): Hitbox {
hitboxStart = Point2d(x1, y1)
hitboxEnd = Point2d(x1 + width, y1 + height)
return this
}
fun setPosition(vector: Vector2) = setPosition(vector.x, vector.y)
fun setPositionX(x: Double) {
setPosition(x, posY)
}
fun setPositionX(x: Double) = setPosition(x, posY)
fun setPositionY(y: Double) = setPosition(posX, y)
fun setPositionY(y: Double) {
setPosition(posX, y)
}
fun setPositionFromPoint(x1: Double, y1: Double) {
fun setPositionFromPoint(x1: Double, y1: Double): Hitbox {
hitboxStart = Point2d(x1 - width / 2, y1 - height)
hitboxEnd = Point2d(hitboxStart.x + width, hitboxStart.y + height)
return this
}
fun setPositionXFromPoint(x: Double) {
setPositionFromPoint(x, pointedY)
}
fun setPositionYFromPoint(y: Double) {
setPositionFromPoint(pointedX, y)
}
fun translatePosX(d: Double) {
fun translatePosX(d: Double): Hitbox {
setPositionX(posX + d)
return this
}
fun translatePosY(d: Double) {
fun translatePosY(d: Double): Hitbox {
setPositionY(posY + d)
return this
}
fun setDimension(w: Double, h: Double) {
fun setDimension(w: Double, h: Double): Hitbox {
width = w
height = h
return this
}
fun snapToPixel(): Hitbox {
hitboxStart.x = Math.round(hitboxStart.x - HALF_PIXEL).toDouble()
hitboxStart.y = Math.round(hitboxStart.y - HALF_PIXEL).toDouble()
hitboxEnd.x = Math.round(hitboxEnd.x - HALF_PIXEL).toDouble()
hitboxEnd.y = Math.round(hitboxEnd.y - HALF_PIXEL).toDouble()
return this
}
/**
@@ -132,9 +128,7 @@ class Hitbox(x1: Double, y1: Double, width: Double, height: Double) {
val centeredY: Double
get() = (hitboxStart.y + hitboxEnd.y) * 0.5f
fun toVector(): Vector2 {
return Vector2(posX, posY)
}
fun toVector(): Vector2 = Vector2(posX, posY)
fun clone(): Hitbox = Hitbox(posX, posY, width, height)
}

View File

@@ -9,8 +9,7 @@ object KeyMap {
var map_code = Hashtable<EnumKeyFunc, Int>()
fun build() {
init {
map_code.put(EnumKeyFunc.MOVE_UP, Key.E)
map_code.put(EnumKeyFunc.MOVE_LEFT, Key.S)
map_code.put(EnumKeyFunc.MOVE_DOWN, Key.D)

View File

@@ -1,6 +1,7 @@
package net.torvald.terrarum.langpack
import net.torvald.CSVFetcher
import net.torvald.JsonFetcher
import net.torvald.imagefont.GameFontWhite
import net.torvald.terrarum.Terrarum
import org.apache.commons.csv.CSVRecord
@@ -14,81 +15,106 @@ import java.util.*
*/
object Lang {
private val CSV_COLUMN_FIRST = "STRING_ID"
/**
* Get record by its STRING_ID
*
* HashMap<"$key_$language", Value>
*/
private var lang: HashMap<String, CSVRecord>
private val FALLBACK_LANG_CODE = "enUS"
private var langpack: HashMap<String, String>
private val FALLBACK_LANG_CODE = "en"
private val HANGUL_SYL_START = 0xAC00
private val PATH_TO_CSV = "./res/locales/"
private val CSV_MAIN = "polyglot.csv"
private val NAMESET_PREFIX = "nameset_"
val languageList: List<String>
private val PATH_TO_LANG = "./res/locales/"
val POLYGLOT_VERSION = "100"
private val PREFIX_POLYGLOT = "Polyglot-${POLYGLOT_VERSION}_"
private val PREFIX_NAMESET = "nameset_"
private val HANGUL_POST_INDEX_ALPH = intArrayOf(// 0: 는, 가, ... 1: 은, 이, ...
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
private val HANGUL_POST_RO_INDEX_ALPH = intArrayOf(// 0: 로 1: 으로
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
private val ENGLISH_WORD_NORMAL_PLURAL = arrayOf("photo")
private val ENGLISH_WORD_NORMAL_PLURAL = arrayOf("photo", "demo")
private val FRENCH_WORD_NORMAL_PLURAL = arrayOf("bal", "banal", "fatal", "final")
var TIPS_COUNT = 0
private set
init {
lang = HashMap<String, CSVRecord>()
langpack = HashMap<String, String>()
val localesDir = File(PATH_TO_LANG)
// append CSV records to the main langpack
val file = File(PATH_TO_CSV)
val filter = FilenameFilter { dir, name -> name.contains(".csv") && !name.contains(NAMESET_PREFIX) }
for (csvfilename in file.list(filter)) {
val csv = CSVFetcher.readCSV(PATH_TO_CSV + csvfilename)
//csv.forEach({ langPackCSV. })
csv.forEach { it -> lang.put(it.get(CSV_COLUMN_FIRST), it) }
// get all of the languages installed
languageList = localesDir.listFiles().filter { it.isDirectory }.map { it.name }
for (lang in languageList) {
// load polyglot first
val polyglotFile = File("$PATH_TO_LANG$lang/$PREFIX_POLYGLOT$lang.json")
val json = JsonFetcher.readJson(polyglotFile)
/*
* Polyglot JSON structure is:
*
* (root object)
* "resources": object
* "polyglot": object
* (polyglot meta)
* "data": array
* [0]: object
* n = "CONTEXT_CHARACTER_CLASS"
* s = "Class"
* [1]: object
* n = "CONTEXT_CHARACTER_DELETE"
* s = "Delecte Character"
* (the array continues)
*
*/
json.getAsJsonObject("resources").getAsJsonArray("data").forEach {
langpack.put(
"${it.asJsonObject["n"].asString}_$lang",
it.asJsonObject["s"].asString
)
}
// and then the rest of the lang file
val langFileList = ArrayList<File>()
// --> filter out files to retrieve a list of valid lang files
val langFileListFiles = File("$PATH_TO_LANG$lang/").listFiles()
langFileListFiles.forEach {
if (!it.name.startsWith("Polyglot") && it.name.endsWith(".json"))
langFileList.add(it)
}
// --> put json entries in langpack
for (langFile in langFileList) {
val json = JsonFetcher.readJson(langFile)
/*
* Terrarum langpack JSON structure is:
*
* (root object)
* "<<STRING ID>>" = "<<LOCALISED TEXT>>"
*/
//println(json.entrySet())
json.entrySet().forEach {
langpack.put("${it.key}_$lang", it.value.asString)
// count up TIPS_COUNT
if (lang == "en" && it.key.startsWith("GAME_TIPS_")) TIPS_COUNT++
}
}
}
// sort word lists
Arrays.sort(ENGLISH_WORD_NORMAL_PLURAL)
Arrays.sort(FRENCH_WORD_NORMAL_PLURAL)
// reload correct (C/J) unihan fonts if applicable
try {
(Terrarum.gameFont as GameFontWhite).reloadUnihan()
}
catch (e: SlickException) {
}
}
fun getRecord(key: String): CSVRecord {
val record = lang[key]
if (record == null) {
println("[Lang] No such record: $key")
throw NullPointerException()
}
return record
}
operator fun get(key: String): String {
fun fallback(): String = lang[key]!!.get(FALLBACK_LANG_CODE)
fun fallback(): String = langpack["${key}_$FALLBACK_LANG_CODE"] ?: "ERRNULL:$key"
var value: String
try {
value = lang[key]!!.get(Terrarum.gameLocale)
// fallback if empty string
if (value.length == 0)
value = fallback()
}
catch (e1: kotlin.KotlinNullPointerException) {
value = "ERRNULL:$key"
}
catch (e: IllegalArgumentException) {
//value = key
value = fallback()
}
val ret = langpack["${key}_${Terrarum.gameLocale}"]
return value
return if (ret.isNullOrEmpty()) fallback() else ret!!
}
fun pluraliseLang(key: String, count: Int): String {
@@ -185,8 +211,4 @@ object Lang {
private fun getLastChar(s: String): Char {
return s[s.length - 1]
}
private fun appendToLangByStringID(record: CSVRecord) {
lang.put(record.get(CSV_COLUMN_FIRST), record)
}
}

View File

@@ -27,12 +27,11 @@ class TileProp {
/**
* @param luminosity Raw RGB value, without alpha
*/
private var realLum: Int = 0
var luminosity: Int
var luminosity: Int = 0
set(value) {
realLum = value
field = value
}
get() = TilePropUtil.getDynamicLumFunc(realLum, dynamicLuminosityFunction)
get() = TilePropUtil.getDynamicLumFunc(field, dynamicLuminosityFunction)
var drop: Int = 0
var dropDamage: Int = 0