com.torvald → net.torvald

Former-commit-id: 375604da8a20a6ba7cd0a8d05a44add02b2d04f4
Former-commit-id: 287287c5920b07618174d7a7573f049d350ded66
This commit is contained in:
Song Minjae
2016-04-12 12:29:02 +09:00
parent 2a34efb489
commit ac9f5b5138
148 changed files with 473 additions and 524 deletions

View File

@@ -0,0 +1,132 @@
## About gamemaking ##
### CHALLENGING, NOT PUNISHING ###
https://www.youtube.com/watch?v=ea6UuRTjkKs
1. CONSISTENT RULES
- No arbitrary unstoppable death
2. Player's skill involved
- Can play around, not restart
3. Usability of in-game tools
- Players should be able to 'regret' their strategy and adjust.
4. Comfortable control
5. Make players overcome the challenge, not defeating them
6. Let players have "aha" moment when they failed.
- Make them hungry to retry with new strategies.
- Some small things they've could done differently
- e.g. "One-big-hit didn't worked, may I should've picked up high DPS one"
### MORE DEPTH, LESS COMPLEXITY ###
https://www.youtube.com/watch?v=jVL4st0blGU
1. Memorise less!
- Less burden to, even starting the game
- Start with gentle learning curve, getting slowly steep
- Intuitive UX (UI, control, ...)
- Good tutorial = lessens complexity
2. Intuitive!
3. Calculations per second
- reduce!
4. Players have to know everything to even begin the play ### FAIL (irreducible complexity)
- Make them get familiar with rules of the game
- Dwarf Fortress failed this!
### Lots of things players can play with (aka don't make them bored) ###
- Combat, battle, building, mechanics, adventure, dungeon explore, spelunking
- Not scaled; easy combat, tough combat, tedious combat, etc.
### Achieving perfect imbalance ###
https://www.youtube.com/watch?v=e31OSVZF77w
- Make sure no matter how you skilled, your playable character cannot be good at everything
- Give players __wide pool of options__ to solve problem
(kill the boss, defend their adobe, fast transportation, etc.)
**_What feeling do you want to convey?_**
### Always think WHY you want to add _something_ on the game ###
- e.g. Why are you adding RPG leveling system? What it would do to the players? How would they play with?
See also: *HEARTS, CLUBS, DIAMONDS, SPADES: PLAYERS WHO SUIT MUDS*
## About this very game ##
### Friendlier version of Dwarf Fortress Adventure mode ###
- Yet _lots of fun_
- Add Fortress mode features by 'make your own settling'
- Hard to actually die, but once you die, you're done.
+ Config: imtooyoungtodie for easy mode
- Genre: Adventure, Open world (towns in RPG, building, town managing (conquer existing one or
you build one and persuade existing people to move in) -> See Dwarf Fortress and Animal Crossing)
* Adventure: adventure this vast—5,5 km wide—world, discover new (and good/horrible) things
* Open world:
- Building: building your own houses, structures, etc.
- Town managing:
1. Build your own little hamlet and manage it
or-
2. Conquer existing one and become a ruler
The town is a special hamlet that can be tailored for your taste
- Survival:
mobs will trying to attack your assets (yourself, your hamlet, your people)
### Side view ###
### Interact menu w/ mouse right ###
### Pixelated sprites ###
- Use 2x sprites if rotating does not work well
### User experience ###
* Indicative mouse cursor
### Game mechanics ###
* 24 pixels == 1 metre
### Purpose of the game ###
* Boss
- Will be mentioned/shown as absolute _evil_.
- But actually is not.
* Theme
- Is an evil really really is what we think?
- Is there a thing as 'absolute evil'?
* Boss character
- From debugger character
- Name key: "Sigriðr hinn Dróttningin" (can be changed)
* Little setting
- A ruler, hated by people
* Mechanics
- Beating boss does not ends the game, but grants an ability to
create new character as it.

View File

@@ -0,0 +1,9 @@
* *Terrarum* by Torvald
Copyright 2015-2016 Torvald. All rights reserved.
mailto: skyhi14 *64* __115875741922660__ *46* __6516589__
* *Simplex Noise Generator*, version 2012-03-09 by Stefan Gustavson
Released as public domain
* *Joise* modular noise library
Copyright (C) 2013 Jason Taylor. Released as open-source under Apache License, Version 2.0.

View File

@@ -0,0 +1,19 @@
package net.torvald.terrarum
import com.google.gson.JsonObject
/**
* Created by minjaesong on 16-03-19.
*/
object DefaultConfig {
fun fetch(): JsonObject {
val jsonObject = JsonObject()
jsonObject.addProperty("smoothlighting", true)
jsonObject.addProperty("imtooyoungtodie", false)
jsonObject.addProperty("language", Terrarum.sysLang)
jsonObject.addProperty("notificationshowuptime", 6500)
return jsonObject
}
}

View File

@@ -0,0 +1,323 @@
package net.torvald.terrarum
import net.torvald.terrarum.gameactors.*
import net.torvald.terrarum.console.Authenticator
import net.torvald.terrarum.gamecontroller.GameController
import net.torvald.terrarum.gamecontroller.Key
import net.torvald.terrarum.gamecontroller.KeyMap
import net.torvald.terrarum.gamecontroller.KeyToggler
import net.torvald.terrarum.gamemap.GameMap
import net.torvald.terrarum.gamemap.WorldTime
import net.torvald.terrarum.mapdrawer.LightmapRenderer
import net.torvald.terrarum.mapdrawer.MapCamera
import net.torvald.terrarum.mapdrawer.MapDrawer
import net.torvald.terrarum.mapgenerator.MapGenerator
import net.torvald.terrarum.mapgenerator.RoguelikeRandomiser
import net.torvald.terrarum.tileproperties.TilePropCodex
import net.torvald.terrarum.tilestats.TileStats
import net.torvald.terrarum.ui.BasicDebugInfoWindow
import net.torvald.terrarum.ui.ConsoleWindow
import net.torvald.terrarum.ui.Notification
import net.torvald.terrarum.ui.UIHandler
import org.lwjgl.opengl.GL11
import org.newdawn.slick.*
import org.newdawn.slick.fills.GradientFill
import org.newdawn.slick.geom.Rectangle
import org.newdawn.slick.state.BasicGameState
import org.newdawn.slick.state.StateBasedGame
import shader.Shader
import java.lang.management.ManagementFactory
import java.util.*
/**
* Created by minjaesong on 16-03-19.
*/
class Game @Throws(SlickException::class)
constructor() : BasicGameState() {
internal var game_mode = 0
lateinit var map: GameMap
val actorContainer = LinkedList<Actor>()
val uiContainer = LinkedList<UIHandler>()
lateinit var consoleHandler: UIHandler
lateinit var debugWindow: UIHandler
lateinit var notifinator: UIHandler
lateinit internal var player: Player
private var GRADIENT_IMAGE: Image? = null
private var skyBox: Rectangle? = null
var screenZoom = 1.0f
val ZOOM_MAX = 2.0f
val ZOOM_MIN = 0.25f
private lateinit var shader12BitCol: Shader
private lateinit var shaderBlurH: Shader
private lateinit var shaderBlurV: Shader
private val useShader: Boolean = false
private val shaderProgram = 0
val memInUse: Long
get() = ManagementFactory.getMemoryMXBean().heapMemoryUsage.used shr 20
val totalVMMem: Long
get() = Runtime.getRuntime().maxMemory() shr 20
val auth = Authenticator()
private var update_delta: Int = 0
val KEY_LIGHTMAP_RENDER = Key.F7
val KEY_LIGHTMAP_SMOOTH = Key.F8
@Throws(SlickException::class)
override fun init(gameContainer: GameContainer, stateBasedGame: StateBasedGame) {
KeyMap.build()
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")
GRADIENT_IMAGE = Image("res/graphics/sky_colour.png")
skyBox = Rectangle(0f, 0f, Terrarum.WIDTH.toFloat(), Terrarum.HEIGHT.toFloat())
TilePropCodex()
// new ItemPropCodex() -- This is kotlin object and already initialised.
map = GameMap(8192, 2048)
map.gravitation = 9.8f
MapGenerator.attachMap(map)
MapGenerator.SEED = 0x51621D2
//mapgenerator.setSeed(new HQRNG().nextLong());
MapGenerator.generateMap()
RoguelikeRandomiser.seed = 0x540198
//RoguelikeRandomiser.setSeed(new HQRNG().nextLong());
// add new player and put it to actorContainer
player = PBSigrid.create()
//player = PFCynthia.create()
//player.setNoClip(true);
addActor(player)
consoleHandler = UIHandler(ConsoleWindow())
consoleHandler.setPosition(0, 0)
debugWindow = UIHandler(BasicDebugInfoWindow())
debugWindow.setPosition(0, 0)
notifinator = UIHandler(Notification())
notifinator.setPosition(
(Terrarum.WIDTH - notifinator.UI.width) / 2, Terrarum.HEIGHT - notifinator.UI.height)
notifinator.setVisibility(true)
if (Terrarum.gameConfig.getAsBoolean("smoothlighting") == true)
KeyToggler.forceSet(KEY_LIGHTMAP_SMOOTH, true)
else
KeyToggler.forceSet(KEY_LIGHTMAP_SMOOTH, false)
}
override fun update(gc: GameContainer, sbg: StateBasedGame, delta: Int) {
update_delta = delta
setAppTitle()
// GL at after_sunrise-noon_before_sunset
map.updateWorldTime(delta)
map.globalLight = globalLightByTime
GameController.processInput(gc.input)
TileStats.update()
MapDrawer.update(gc, delta)
MapCamera.update(gc, delta)
actorContainer.forEach { actor -> actor.update(gc, delta) }
actorContainer.forEach { actor ->
if (actor is Visible) {
actor.updateBodySprite(gc, delta)
}
if (actor is Glowing) {
actor.updateGlowSprite(gc, delta)
}
}
uiContainer.forEach { ui -> ui.update(gc, delta) }
consoleHandler.update(gc, delta)
debugWindow.update(gc, delta)
notifinator.update(gc, delta)
Terrarum.appgc.setVSync(Terrarum.appgc.fps >= Terrarum.VSYNC_TRIGGER_THRESHOLD)
}
private fun setAppTitle() {
Terrarum.appgc.setTitle(
"Simple Slick Game — FPS: "
+ Terrarum.appgc.fps + " ("
+ Terrarum.TARGET_INTERNAL_FPS.toString()
+ ") — "
+ memInUse.toString() + "M / "
+ totalVMMem.toString() + "M")
}
override fun render(gc: GameContainer, sbg: StateBasedGame, g: Graphics) {
setBlendNormal()
Terrarum.gameConfig["smoothlighting"] = KeyToggler.isOn(KEY_LIGHTMAP_SMOOTH)
if (!g.isAntiAlias) g.isAntiAlias = true
drawSkybox(g)
// compensate for zoom. UIs have to be treated specially! (see UIHandler)
g.translate(
-MapCamera.cameraX * screenZoom, -MapCamera.cameraY * screenZoom)
MapCamera.renderBehind(gc, g)
actorContainer.forEach { actor -> if (actor is Visible) actor.drawBody(gc, g) }
player.drawBody(gc, g)
LightmapRenderer.renderLightMap()
MapCamera.renderFront(gc, g)
MapDrawer.render(gc, g)
setBlendMul()
MapDrawer.drawEnvOverlay(g)
if (!KeyToggler.isOn(KEY_LIGHTMAP_RENDER)) setBlendMul() else setBlendNormal()
LightmapRenderer.draw(g)
setBlendNormal()
actorContainer.forEach { actor -> if (actor is Glowing) actor.drawGlow(gc, g) }
player.drawGlow(gc, g)
uiContainer.forEach { ui -> ui.render(gc, g) }
debugWindow.render(gc, g)
consoleHandler.render(gc, g)
notifinator.render(gc, g)
}
private fun getGradientColour(row: Int): Color {
val gradMapWidth = GRADIENT_IMAGE!!.width
val phase = Math.round(
map.worldTime.elapsedSeconds().toFloat() / WorldTime.DAY_LENGTH.toFloat() * gradMapWidth
)
//update in every INTERNAL_FRAME frames
return GRADIENT_IMAGE!!.getColor(phase, row)
}
override fun keyPressed(key: Int, c: Char) {
GameController.keyPressed(key, c)
}
override fun keyReleased(key: Int, c: Char) {
GameController.keyReleased(key, c)
}
override fun mouseMoved(oldx: Int, oldy: Int, newx: Int, newy: Int) {
GameController.mouseMoved(oldx, oldy, newx, newy)
}
override fun mouseDragged(oldx: Int, oldy: Int, newx: Int, newy: Int) {
GameController.mouseDragged(oldx, oldy, newx, newy)
}
override fun mousePressed(button: Int, x: Int, y: Int) {
GameController.mousePressed(button, x, y)
}
override fun mouseReleased(button: Int, x: Int, y: Int) {
GameController.mouseReleased(button, x, y)
}
override fun mouseWheelMoved(change: Int) {
GameController.mouseWheelMoved(change)
}
override fun controllerButtonPressed(controller: Int, button: Int) {
GameController.controllerButtonPressed(controller, button)
}
override fun controllerButtonReleased(controller: Int, button: Int) {
GameController.controllerButtonReleased(controller, button)
}
override fun getID(): Int {
return Terrarum.SCENE_ID_GAME
}
private fun drawSkybox(g: Graphics) {
val skyColourFill = GradientFill(
0f, 0f, getGradientColour(0),
0f, Terrarum.HEIGHT.toFloat(), getGradientColour(1)
)
g.fill(skyBox, skyColourFill)
}
fun sendNotification(msg: Array<String>) {
(notifinator.UI as Notification).sendNotification(Terrarum.appgc, update_delta, msg)
notifinator.setAsOpening()
}
private val globalLightByTime: Int
get() = getGradientColour(2).getRGB24().rgb24ExpandToRgb30()
/**
* extension function for org.newdawn.slick.Color
*/
fun Color.getRGB24(): Int = (this.redByte shl 16) or (this.greenByte shl 8) or (this.blueByte)
fun Int.rgb24ExpandToRgb30(): Int = (this and 0xff) or
(this and 0xff00).ushr(8).shl(10) or
(this and 0xff0000).ushr(16).shl(20)
/**
* actorContainer extensions
*/
fun hasActor(ID: Long): Boolean {
for (actor in actorContainer) {
if (actor.referenceID == ID) return true
}
return false
}
fun removeActor(ID: Long) {
for (actor in actorContainer) {
if (actor.referenceID == ID)
actorContainer.remove(actor)
}
}
fun addActor(other: Actor): Boolean {
if (hasActor(other.referenceID)) return false
actorContainer.add(other)
return true
}
fun getActor(ID: Long): Actor {
for (actor in actorContainer) {
if (actor.referenceID == ID)
return actor
}
throw NullPointerException("Actor with ID $ID does not exist.")
}
fun addUI(other: UIHandler): Boolean {
if (uiContainer.contains(other)) return false
uiContainer.add(other)
return true
}
}

View File

@@ -0,0 +1,6 @@
package net.torvald.terrarum
/**
* Created by minjaesong on 16-03-19.
*/
class GameConfig : KVHashMap()

View File

@@ -0,0 +1,97 @@
package net.torvald.terrarum
import com.google.gson.JsonPrimitive
import java.util.*
/**
* Created by minjaesong on 16-03-19.
*/
open class KVHashMap {
private val hashMap: HashMap<String, Any>
init {
hashMap = HashMap<String, Any>()
}
/**
* Add key-value pair to the configuration table.
* If key does not exist on the table, new key will be generated.
* If key already exists, the value will be overwritten.
* @param key case insensitive
* *
* @param value
*/
operator fun set(key: String, value: Any) {
hashMap.put(key.toLowerCase(), value)
}
/**
* Get value using key from configuration table.
* @param key case insensitive
* *
* @return Object value
*/
operator fun get(key: String): Any? {
return hashMap[key.toLowerCase()]
}
fun getAsInt(key: String): Int? {
val value = get(key)
if (value is JsonPrimitive)
return value.asInt
return get(key) as Int?
}
fun getAsFloat(key: String): Float? {
val value = get(key)
if (value is Int)
return value.toFloat()
else if (value is JsonPrimitive)
return value.asFloat
return value as Float?
}
fun getAsDouble(key: String): Double? {
val value = get(key)
if (value is Int)
return value.toDouble()
else if (value is JsonPrimitive)
return value.asDouble
return value as Double?
}
fun getAsString(key: String): String? {
val value = get(key)
if (value is JsonPrimitive)
return value.asString
return value as String?
}
fun getAsBoolean(key: String): Boolean? {
val value = get(key)
if (value is JsonPrimitive)
return value.asBoolean
return value as Boolean?
}
fun hasKey(key: String): Boolean {
return hashMap.containsKey(key)
}
val keySet: Set<Any>
get() = hashMap.keys
}

View File

@@ -0,0 +1,119 @@
## Weapon tier ##
Natural / Common Stone -> Copper -> Iron -> Silver -> Titanium
Forging --------------> Steel --------^
Exotic ('elven') Glass Aurichalcum
Special (something 'adamant') ??? (Use material spec of CNT, tensile strength 180 GPa)
* Metal graphics
- Gold: Hue 43, low Saturation
- Aurichalcum: Hue 43, mid-high Saturation
- Copper: Hue 33,
- Copper rust: Hue 160
- Iron rust: Hue 21
## Size variation ##
Race base weapon/tool size <- 10 [kg]
Size tolerance <- (50% * str/1000), or say, 20%
If the size is bigger than tolerable, weapon speed severely slows down, tools become unusable
if use time >* 0.75 second, the weapon/tool cannot be equipped.
Small weapons/tools gains no (dis)advantage
When drawing: scale by (craftedWeaponSize / baseWeaponSize)
Crafted tool/weapon size is dependent to the baseRaceMass.
## Gemstone tier ##
Topaz -> R·G·B -> Diamond·Amethyst
## Colouring ##
Natural: Use 4096
Magical/Surreal: Use 24 Bits
* Colouring of potion
- Randomised, roguelike fashion
- Choose Col(R40, G40, B40) from set of finite cards:
39, 39, 19, 19, 0, 0
- MULTIPLY blend chosen colour with white texture
## Roguelike identity ##
* Randomised things
- E.g. potion
Lime-coloured potion
First play: "Potion (???)"
After drank: "Potion (Healing)" is revealed.
Second (new) play: "Potion (???)"
After drank: "Potion (Neurotoxin)" is revealed.
## Making sprite ##
* Layers
- (Optional) Glow
- (Optional) Hair foreground
- Right arm dress
- Right arm body
- Dress
- Boots
- Body
- (Optional) Hair accessory
- Hair
- Head
- Left arm dress
- Left arm body
- (Optional) SFX
* Size
- Regular sprite 'height' (hitbox height) : 40 px
- Apparent height may vary
## Chargen ##
* Select hair, colours, then compile them into single spritesheet
* NO gender distinction, but have masculine/neutral/feminine looks (in clothing, hairstyles, etc.)
* Colour: 4096 colours (12-bit 0x000 - 0xFFF)
* Base mass: 60 kg
## Food/Potion dose ##
Scale ^ 3 ^ (3/4) == (ThisWgt / TargetWgt) ^ (3/4)
## (De)serialisation ##
see SAVE_FORMAT.md
## Actor as universal tool ##
* Utility tiles that have states (e.g. noteblock) are implemented using Actor.
## NPC Killing ##
* AI:
Attacked first: Warn player to not attack
Attacked second: The NPC becomes hostile until player sheathe the weapon
Attacked thrice: All the NPCs within the same faction become hostile until the player is away
## Ownership of lands ##
* Codex of Real Estate → assign owner to the tiles → copy the information to the NPC instance

View File

@@ -0,0 +1,34 @@
## Gadgets ##
### Looms for custom pattern ###
- Players can create their own décors (hang on wall), dresses.
- Two looms (216 colour mode, 4096 colour mode)
### Music making ###
- Automated glockenspiel thingy
- Single tile can hold 48 notes, single track
- Work like Modtracker, incl. physical arrangements
Arrangements in the map
Time →→→→
voice 1 → □□□□□□□□□□□□□□□□...
voice 2 → □□□□□□□□□□□□□□□□...
↑ played simultaneously along the X-axis
- Each tracker head and body are connected by placing tracks adjacent to each other or connecting them with wire.
Connect two or more tracker head to play the array of trackers play simultaneously (multi-tracking)
- Serialisation
<actorid>.json
{
notes = [arr<int>, fixed size of 48],
speed = 120
}
*int: (0-63) number of the note pitch that is struck. 32: Middle C (C3).
'A' just above of Middle C (A3) has base pitch of 440 Hz.
*speed: in BPM

View File

@@ -0,0 +1,56 @@
## Format ##
* Save meta
- GZip'd binary (for more security)
- Filename : world (with no extension)
|Type |Mnemonic |Description |
|------------|------------|---------------------------------------|
|Byte[4] |TESV |Magic |
|Byte[n] |name |Savegame name, UTF-8 |
|Byte |NULL |String terminator |
|Byte[8] |terraseed |Terrain seed |
|Byte[8] |rogueseed |Randomiser seed |
|Byte[32] |hash1 |SHA-256 hash of worldinfo1 being stored (when not zipped)|
|Byte[32] |hash2 |SHA-256 hash of worldinfo2 being stored (when not zipped)|
|Byte[32] |hash3 |SHA-256 hash of worldinfo3 being stored (when not zipped)|
|Byte[32] |hash4 |SHA-256 hash of worldinfo4 being stored (when not zipped)|
Endianness: Big
* Actor/Faction data
- GZip'd GSON
- Filename : (refid) (with no extension)
* Prop data
- GZip'd CSV
- Filename : (with no extension)
worldinfo2 -- tileprop
worldinfo3 -- itemprop
worldinfo4 -- materialprop
* Human-readable
- Tiles_list.txt -- list of tiles in csv
- Items_list.txt -- list of items in csv
- Materials_list.txt -- list of materials in csv
## How it works ##
* If hash discrepancy has detected, (hash of csv in save dir != stored hash || hash of TEMD != stored hash), printout "Save file corrupted. Continue?" with prompt "Yes/No"
Directory:
+--- <save1>
--- 2a93bc5fd...f823 Actor/Faction/etc. data
--- 423bdc838...93bd Actor/Faction/etc. data
--- Items_list.txt Human-readable
--- Materials_list.txt Human-readable
--- Tiles_list.txt Human-readable
--- world save meta (binary, GZip)
--- worldinfo1 TEMD (binary, GZip)
--- worldinfo2 tileprop (GZip)
--- worldinfo3 itemprop (GZip)
--- worldinfo4 materialprop (GZip)

View File

@@ -0,0 +1,377 @@
package net.torvald.terrarum
import net.torvald.imagefont.GameFontWhite
import net.torvald.JsonFetcher
import net.torvald.JsonWriter
import org.lwjgl.input.Controllers
import org.lwjgl.opengl.GL11
import org.newdawn.slick.AppGameContainer
import org.newdawn.slick.Font
import org.newdawn.slick.GameContainer
import org.newdawn.slick.SlickException
import org.newdawn.slick.state.StateBasedGame
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
import java.util.logging.FileHandler
import java.util.logging.Level
import java.util.logging.Logger
import java.util.logging.SimpleFormatter
/**
* Created by minjaesong on 15-12-30.
* Kotlin code: Created by minjaesong on 16-03-19.
*/
class Terrarum @Throws(SlickException::class)
constructor(gamename: String) : StateBasedGame(gamename) {
init {
gameConfig = GameConfig()
getDefaultDirectory()
createDirs()
val readFromDisk = readConfigJson()
if (!readFromDisk) readConfigJson()
// get locale from config
gameLocale = gameConfig.getAsString("language") ?: sysLang
// if bad game locale were set, use system locale
if (gameLocale.length < 4)
gameLocale = sysLang
println("[terrarum] Locale: " + gameLocale)
}
@Throws(SlickException::class)
override fun initStatesList(gc: GameContainer) {
gameFont = GameFontWhite()
hasController = gc.input.controllerCount > 0
if (hasController) {
for (c in 0..Controllers.getController(0).axisCount - 1) {
Controllers.getController(0).setDeadZone(c, CONTROLLER_DEADZONE)
}
}
appgc.input.enableKeyRepeat()
game = Game()
addState(game)
}
companion object {
/**
* To be used with physics simulator
*/
val TARGET_FPS = 50
/**
* To be used with render, to achieve smooth frame drawing
* TARGET_INTERNAL_FPS > TARGET_FPS for smooth frame drawing
* Must choose a value so that (1000 / VAL) is still integer
*/
val TARGET_INTERNAL_FPS = 100
lateinit var appgc: AppGameContainer
val WIDTH = 1060
val HEIGHT = 742 // IMAX ratio
var VSYNC = true
val VSYNC_TRIGGER_THRESHOLD = 56
lateinit var game: Game
lateinit var gameConfig: GameConfig
lateinit var OSName: String
private set
lateinit var OSVersion: String
private set
lateinit var OperationSystem: String
private set
lateinit var defaultDir: String
private set
lateinit var defaultSaveDir: String
private set
var gameLocale = "" // locale override
lateinit var gameFont: Font
val SCENE_ID_HOME = 1
val SCENE_ID_GAME = 3
var hasController = false
val CONTROLLER_DEADZONE = 0.1f
private lateinit var configDir: String
fun main(args: Array<String>) {
try {
appgc = AppGameContainer(Terrarum("Terrarum"))
appgc.setDisplayMode(WIDTH, HEIGHT, false)
appgc.setTargetFrameRate(TARGET_INTERNAL_FPS)
appgc.setVSync(VSYNC)
appgc.setMaximumLogicUpdateInterval(1000 / TARGET_INTERNAL_FPS)
appgc.setMinimumLogicUpdateInterval(1000 / TARGET_INTERNAL_FPS - 1)
appgc.setShowFPS(false)
appgc.setUpdateOnlyWhenVisible(false)
appgc.start()
}
catch (ex: SlickException) {
val logger = Logger.getLogger(Terrarum::class.java.name)
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
val calendar = Calendar.getInstance()
val filepath = "$defaultDir/crashlog-${dateFormat.format(calendar.time)}.txt"
val fileHandler = FileHandler(filepath)
logger.addHandler(fileHandler)
val formatter = SimpleFormatter()
fileHandler.formatter = formatter
//logger.info()
println("The game has been crashed!")
println("Crash log were saved to $filepath.")
println("================================================================================")
logger.log(Level.SEVERE, null, ex)
}
}
private fun getDefaultDirectory() {
OSName = System.getProperty("os.name")
OSVersion = System.getProperty("os.version")
val OS = System.getProperty("os.name").toUpperCase()
if (OS.contains("WIN")) {
OperationSystem = "WINDOWS"
defaultDir = System.getenv("APPDATA") + "/terrarum"
}
else if (OS.contains("OS X")) {
OperationSystem = "OSX"
defaultDir = System.getProperty("user.home") + "/Library/Application Support/terrarum"
}
else if (OS.contains("NUX") || OS.contains("NIX")) {
OperationSystem = "LINUX"
defaultDir = System.getProperty("user.home") + "/.terrarum"
}
else if (OS.contains("SUNOS")) {
OperationSystem = "SOLARIS"
defaultDir = System.getProperty("user.home") + "/.terrarum"
}
else {
OperationSystem = "UNKNOWN"
defaultDir = System.getProperty("user.home") + "/.terrarum"
}
defaultSaveDir = defaultDir + "/Saves"
configDir = defaultDir + "/config.json"
}
private fun createDirs() {
val dirs = arrayOf(File(defaultSaveDir))
for (d in dirs) {
if (!d.exists()) {
d.mkdirs()
}
}
}
@Throws(IOException::class)
private fun createConfigJson() {
val configFile = File(configDir)
if (!configFile.exists() || configFile.length() == 0L) {
JsonWriter.writeToFile(DefaultConfig.fetch(), configDir)
}
}
private fun readConfigJson(): Boolean {
try {
// read from disk and build config from it
val jsonObject = JsonFetcher.readJson(configDir)
// make config
jsonObject.entrySet().forEach { entry -> gameConfig[entry.key] = entry.value }
return true
}
catch (e: IOException) {
// write default config to game dir. Call this method again to read config from it.
try {
createConfigJson()
}
catch (e1: IOException) {
e.printStackTrace()
}
return false
}
}
// exception handling
val sysLang: String
get() {
val lan = System.getProperty("user.language")
var country = System.getProperty("user.country")
if (lan == "en")
country = "US"
else if (lan == "fr")
country = "FR"
else if (lan == "de")
country = "DE"
else if (lan == "ko") country = "KR"
return lan + country
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
fun getConfigInt(key: String): Int {
var cfg: Int = 0
try {
cfg = gameConfig.getAsInt(key)!!
}
catch (e: NullPointerException) {
// if the config set does not have the key, try for the default config
try {
cfg = DefaultConfig.fetch().get(key).asInt
}
catch (e1: NullPointerException) {
e.printStackTrace()
}
}
catch (e: TypeCastException) {
// if the config set does not have the key, try for the default config
try {
cfg = DefaultConfig.fetch().get(key).asInt
}
catch (e1: kotlin.TypeCastException) {
e.printStackTrace()
}
}
return cfg
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
fun getConfigFloat(key: String): Float {
var cfg = 0f
try {
cfg = gameConfig.getAsFloat(key)!!
}
catch (e: NullPointerException) {
try {
cfg = DefaultConfig.fetch().get(key).asFloat
}
catch (e1: NullPointerException) {
e.printStackTrace()
}
}
return cfg
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
fun getConfigString(key: String): String {
var cfg = ""
try {
cfg = gameConfig.getAsString(key)!!
}
catch (e: NullPointerException) {
try {
cfg = DefaultConfig.fetch().get(key).asString
}
catch (e1: NullPointerException) {
e.printStackTrace()
}
}
return cfg
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
fun getConfigBoolean(key: String): Boolean {
var cfg = false
try {
cfg = gameConfig.getAsBoolean(key)!!
}
catch (e: NullPointerException) {
try {
cfg = DefaultConfig.fetch().get(key).asBoolean
}
catch (e1: NullPointerException) {
e.printStackTrace()
}
}
return cfg
}
}
}
fun main(args: Array<String>) = Terrarum.main(args)
fun setBlendMul() {
GL11.glEnable(GL11.GL_BLEND)
GL11.glColorMask(true, true, true, true)
GL11.glBlendFunc(GL11.GL_DST_COLOR, GL11.GL_ONE_MINUS_SRC_ALPHA)
}
fun setBlendNormal() {
GL11.glEnable(GL11.GL_BLEND)
GL11.glColorMask(true, true, true, true)
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA)
}
fun setBlendAlphaMap() {
GL11.glDisable(GL11.GL_BLEND)
GL11.glColorMask(false, false, false, true)
}
fun setBlendDisable() {
GL11.glDisable(GL11.GL_BLEND)
}

View File

@@ -0,0 +1,46 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.ui.ConsoleWindow
import org.apache.commons.codec.digest.DigestUtils
/**
* Created by minjaesong on 16-02-19.
*/
class Authenticator : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 2) {
val pwd = args[1]
val hashedPwd = DigestUtils.sha256Hex(pwd)
if ("54c5b3dd459d5ef778bb2fa1e23a5fb0e1b62ae66970bcb436e8f81a1a1a8e41".equals(hashedPwd, ignoreCase = true)) {
// alpine
val msg = if (a) "Locked" else "Authenticated"
Echo().execute(msg)
println("[Authenticator] " + msg)
a = !a
(Terrarum.game.consoleHandler.UI as ConsoleWindow).reset()
}
else {
printUsage() // thou shalt not pass!
}
}
else {
printUsage()
}
}
fun b(): Boolean {
return a
}
override fun printUsage() {
CommandInterpreter.echoUnknownCmd("auth")
}
companion object {
private var a = false
}
}

View File

@@ -0,0 +1,24 @@
package net.torvald.terrarum.console
import java.nio.file.FileSystems
import java.nio.file.Files
/**
* Created by minjaesong on 16-03-07.
*/
class Batch : ConsoleCommand {
@Throws(Exception::class)
override fun execute(args: Array<String>) {
if (args.size == 2) {
Files.lines(FileSystems.getDefault().getPath(args[1])).forEach(
{ CommandInterpreter.execute(it) })
}
else {
printUsage()
}
}
override fun printUsage() {
Echo().execute("batch path/to/batch.txt")
}
}

View File

@@ -0,0 +1,32 @@
package net.torvald.terrarum.console
import java.io.IOException
import java.nio.file.FileSystems
import java.nio.file.Files
/**
* Created by minjaesong on 16-02-10.
*/
class CatStdout : ConsoleCommand {
override fun execute(args: Array<String>) {
val echo = Echo()
if (args.size == 1) {
printUsage()
return
}
try {
Files.lines(FileSystems.getDefault().getPath(args[1])).forEach({ echo.execute(it) })
}
catch (e: IOException) {
echo.execute("CatStdout: could not read file -- IOException")
}
}
override fun printUsage() {
Echo().execute("usage: cat 'path/to/text/file")
}
}

View File

@@ -0,0 +1,45 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Game
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.ui.ConsoleWindow
import java.util.Formatter
/**
* Created by minjaesong on 16-01-16.
*/
class CodexEdictis : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 1) {
printList()
}
else {
try {
val commandObj = CommandDict.getCommand(args[1].toLowerCase())
commandObj.printUsage()
}
catch (e: NullPointerException) {
val sb = StringBuilder()
val formatter = Formatter(sb)
Echo().execute("Codex: " + formatter.format(Lang.get("DEV_MESSAGE_CONSOLE_COMMAND_UNKNOWN"), args[1]).toString())
}
}
}
override fun printUsage() {
val echo = Echo()
echo.execute("Usage: codex (command)")
echo.execute("shows how to use 'command'")
echo.execute("leave blank to get list of available commands")
}
private fun printList() {
val echo = Echo()
echo.execute(Lang.get("DEV_MESSAGE_CONSOLE_AVAILABLE_COMMANDS"))
CommandDict.dict.keys.forEach { s -> echo.execute("] " + s) }
}
}

View File

@@ -0,0 +1,47 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Terrarum
import java.util.HashMap
/**
* Created by minjaesong on 16-01-15.
*/
object CommandDict {
internal var dict: HashMap<String, ConsoleCommand> = hashMapOf(
Pair("echo", Echo()),
Pair("setav", SetAV()),
Pair("qqq", QuitApp()),
Pair("codex", CodexEdictis()),
Pair("export", ExportMap()),
Pair("gc", ForceGC()),
Pair("getav", GetAV()),
Pair("getlocale", GetLocale()),
Pair("togglenoclip", ToggleNoClip()),
Pair("nc", ToggleNoClip()),
Pair("setlocale", SetLocale()),
Pair("zoom", Zoom()),
Pair("teleport", TeleportPlayer()),
Pair("tp", TeleportPlayer()),
Pair("cat", CatStdout()),
Pair("exportav", ExportAV()),
Pair("setgl", SetGlobalLightLevel()),
Pair("getfaction", GetFactioning()),
Pair("auth", Terrarum.game.auth),
Pair("spawnball", SpawnPhysTestBall()),
Pair("batch", Batch()),
Pair("settime", SetTime()),
Pair("gettime", GetTime()),
Pair("settimedelta", SetTimeDelta()),
Pair("help", Help()),
// Test codes
Pair("bulletintest", SetBulletin()),
Pair("gsontest", GsonTest())
)
fun getCommand(commandName: String): ConsoleCommand {
return dict[commandName]!!
}
}

View File

@@ -0,0 +1,116 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.Terrarum
import java.util.ArrayList
import java.util.Formatter
import java.util.regex.Pattern
/**
* Created by minjaesong on 16-01-15.
*/
object CommandInterpreter {
private val commandsNoAuth = arrayOf("auth", "qqq", "zoom", "setlocale", "getlocale", "help")
fun execute(command: String) {
val cmd = parse(command)
for (single_command in cmd) {
var commandObj: ConsoleCommand? = null
try {
if (commandsNoAuth.contains(single_command!!.name.toLowerCase())) {
commandObj = CommandDict.getCommand(single_command.name.toLowerCase())
}
else {
if (Terrarum.game.auth.b()) {
commandObj = CommandDict.getCommand(
single_command.name.toLowerCase())
}
else {
// System.out.println("ee1");
throw NullPointerException() // if not authorised, say "Unknown command"
}
}
}
catch (e: NullPointerException) {
}
finally {
try {
if (commandObj != null) {
commandObj.execute(single_command!!.toStringArray())
}
else {
echoUnknownCmd(single_command!!.name)
// System.out.println("ee3");
}
}
catch (e: Exception) {
println("[CommandInterpreter] :")
e.printStackTrace()
Echo().execute(Lang.get("ERROR_GENERIC_TEXT"))
}
}
}
}
private fun parse(input: String): Array<CommandInput?> {
val patternCommands = Pattern.compile("[^;]+")
val patternTokensInCommand = Pattern.compile("[\"'][^;]+[\"']|[^ ]+")
val commands = ArrayList<String>()
// split multiple commands
var m = patternCommands.matcher(input)
while (m.find()) commands.add(m.group())
// split command tokens from a command
val parsedCommands = arrayOfNulls<CommandInput>(commands.size)
for (i in parsedCommands.indices) {
val tokens = ArrayList<String>()
m = patternTokensInCommand.matcher(commands[i])
while (m.find()) {
val regexGroup = m.group().replace("[\"\']".toRegex(), "")
tokens.add(regexGroup)
}
// create new command
parsedCommands[i] = CommandInput(tokens.toArray())
}
return parsedCommands
}
internal fun echoUnknownCmd(cmdname: String) {
val sb = StringBuilder()
val formatter = Formatter(sb)
Echo().execute(
formatter.format(Lang.get("DEV_MESSAGE_CONSOLE_COMMAND_UNKNOWN"), cmdname).toString())
}
private class CommandInput(o: Array<Any>) {
private val tokens: Array<String>
init {
tokens = Array<String>(o.size, { i -> o[i] as String })
}
fun toStringArray(): Array<String> {
return tokens
}
val name: String
get() = tokens[0]
val argsCount: Int
get() = tokens.size
}
}

View File

@@ -0,0 +1,13 @@
package net.torvald.terrarum.console
/**
* Created by minjaesong on 16-01-15.
*/
interface ConsoleCommand {
@Throws(Exception::class)
fun execute(args: Array<String>)
fun printUsage()
}

View File

@@ -0,0 +1,26 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.ui.ConsoleWindow
import java.util.Arrays
/**
* Created by minjaesong on 16-01-16.
*/
internal class Echo : ConsoleCommand {
override fun execute(args: Array<String>) {
val argsWoHeader = Array<String>(args.size - 1, {it -> args[it + 1]})
argsWoHeader.forEach(
{ (Terrarum.game.consoleHandler.UI as ConsoleWindow).sendMessage(it) })
}
fun execute(single_line: String) {
(Terrarum.game.consoleHandler.UI as ConsoleWindow).sendMessage(single_line)
}
override fun printUsage() {
}
}

View File

@@ -0,0 +1,38 @@
package net.torvald.terrarum.console
import net.torvald.JsonWriter
import net.torvald.terrarum.Terrarum
import java.io.IOException
/**
* Created by minjaesong on 16-02-10.
*/
class ExportAV : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 2) {
try {
JsonWriter.writeToFile(
Terrarum.game.player.actorValue,
Terrarum.defaultDir + "/Exports/" + args[1] + ".json")
Echo().execute("ExportAV: exported to " + args[1] + ".json")
}
catch (e: IOException) {
Echo().execute("ExportAV: IOException raised.")
e.printStackTrace()
}
}
else {
printUsage()
}
}
override fun printUsage() {
val echo = Echo()
echo.execute("Export ActorValue as JSON format.")
echo.execute("Usage: exportav (id) filename-without-extension")
echo.execute("blank ID for player")
}
}

View File

@@ -0,0 +1,116 @@
package net.torvald.terrarum.console
import net.torvald.colourutil.Col4096
import net.torvald.RasterWriter
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.tileproperties.TileNameCode
import java.io.*
import java.util.HashMap
/**
* Created by minjaesong on 16-01-17.
*/
class ExportMap : ConsoleCommand {
//private var mapData: ByteArray? = null
// private var mapDataPointer = 0
private val colorTable = HashMap<Int, Col4096>()
override fun execute(args: Array<String>) {
if (args.size == 2) {
buildColorTable()
var mapData = ByteArray(Terrarum.game.map.width * Terrarum.game.map.height * 3)
var mapDataPointer = 0
for (tile in Terrarum.game.map.terrainIterator()) {
val colArray = (colorTable as Map<Int, Col4096>)
.getOrElse(tile, { Col4096(0xFFF) }).toByteArray()
for (i in 0..2) {
mapData[mapDataPointer + i] = colArray[i]
}
mapDataPointer += 3
}
val dir = Terrarum.defaultDir + "/Exports/"
val dirAsFile = File(dir)
if (!dirAsFile.exists()) {
dirAsFile.mkdir()
}
try {
RasterWriter.writePNG_RGB(
Terrarum.game.map.width, Terrarum.game.map.height, mapData, dir + args[1] + ".png")
Echo().execute("ExportMap: exported to " + args[1] + ".png")
}
catch (e: IOException) {
Echo().execute("ExportMap: IOException raised.")
e.printStackTrace()
}
// mapData = null
// mapDataPointer = 0
// Free up some memory
System.gc()
}
else {
printUsage()
}
}
override fun printUsage() {
val echo = Echo()
echo.execute("Usage: export <name>")
echo.execute("Exports current map into visible image.")
echo.execute("The image can be found at %adddata%/terrarum/Exports")
}
private fun buildColorTable() {
colorTable.put(TileNameCode.AIR, Col4096(0xCEF))
colorTable.put(TileNameCode.STONE, Col4096(0x888))
colorTable.put(TileNameCode.DIRT, Col4096(0x753))
colorTable.put(TileNameCode.GRASS, Col4096(0x472))
colorTable.put(TileNameCode.ORE_COPPER, Col4096(0x6A8))
colorTable.put(TileNameCode.ORE_IRON, Col4096(0xC75))
colorTable.put(TileNameCode.ORE_GOLD, Col4096(0xA87))
colorTable.put(TileNameCode.ORE_ILMENITE, Col4096(0x8AB))
colorTable.put(TileNameCode.ORE_AURICHALCUM, Col4096(0xD92))
colorTable.put(TileNameCode.ORE_SILVER, Col4096(0xDDD))
colorTable.put(TileNameCode.RAW_DIAMOND, Col4096(0x2BF))
colorTable.put(TileNameCode.RAW_RUBY, Col4096(0xB10))
colorTable.put(TileNameCode.RAW_EMERALD, Col4096(0x0B1))
colorTable.put(TileNameCode.RAW_SAPPHIRE, Col4096(0x01B))
colorTable.put(TileNameCode.RAW_TOPAZ, Col4096(0xC70))
colorTable.put(TileNameCode.RAW_AMETHYST, Col4096(0x70C))
colorTable.put(TileNameCode.WATER, Col4096(0x038))
colorTable.put(TileNameCode.LAVA, Col4096(0xF50))
colorTable.put(TileNameCode.SAND, Col4096(0xDDB))
colorTable.put(TileNameCode.SAND_WHITE, Col4096(0xFFD))
colorTable.put(TileNameCode.SAND_RED, Col4096(0xA32))
colorTable.put(TileNameCode.SAND_DESERT, Col4096(0xEDB))
colorTable.put(TileNameCode.SAND_BLACK, Col4096(0x444))
colorTable.put(TileNameCode.SAND_GREEN, Col4096(0x9A6))
colorTable.put(TileNameCode.GRAVEL, Col4096(0x664))
colorTable.put(TileNameCode.GRAVEL_GREY, Col4096(0x999))
colorTable.put(TileNameCode.ICE_NATURAL, Col4096(0x9AB))
colorTable.put(TileNameCode.ICE_MAGICAL, Col4096(0x7AC))
colorTable.put(TileNameCode.ICE_FRAGILE, Col4096(0x6AF))
colorTable.put(TileNameCode.SNOW, Col4096(0xCDE))
}
}

View File

@@ -0,0 +1,15 @@
package net.torvald.terrarum.console
/**
* Created by minjaesong on 16-01-18.
*/
class ForceGC : ConsoleCommand {
override fun execute(args: Array<String>) {
System.gc()
Echo().execute("Invoked System.gc")
}
override fun printUsage() {
Echo().execute("Invoke garbage collection of JVM.")
}
}

View File

@@ -0,0 +1,57 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.gameactors.ActorValue
import net.torvald.terrarum.Game
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-01-19.
*/
class GetAV : ConsoleCommand {
override fun execute(args: Array<String>) {
val echo = Echo()
try {
if (args.size == 1) {
// print all actorvalue of player
val av = Terrarum.game.player.actorValue
val keyset = av.keySet
keyset.forEach { elem -> echo.execute("$elem = ${av[elem as String]}") }
}
else if (args.size != 3 && args.size != 2) {
printUsage()
}
else if (args.size == 2) {
echo.execute("player." + args[1] + " = "
+ Terrarum.game.player.actorValue[args[1]]
+ " ("
+ Terrarum.game.player.actorValue[args[1]]!!.javaClass.simpleName
+ ")")
}
else if (args.size == 3) {
}
}
catch (e: NullPointerException) {
if (args.size == 2) {
echo.execute(args[1] + ": actor value does not exist.")
}
else if (args.size == 3) {
echo.execute(args[2] + ": actor value does not exist.")
}
else {
throw NullPointerException()
}
}
}
override fun printUsage() {
val echo = Echo()
echo.execute("Get desired actor value of specific target.")
echo.execute("Usage: getav (id) <av>")
echo.execute("blank ID for player")
}
}

View File

@@ -0,0 +1,51 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.gameactors.faction.Faction
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.Terrarum
import java.util.HashSet
/**
* Created by minjaesong on 16-02-17.
*/
class GetFactioning : ConsoleCommand {
private val PRINT_INDENTATION = " --> "
override fun execute(args: Array<String>) {
val echo = Echo()
if (args.size == 1) {
// get all factioning data of player
val factionSet = Terrarum.game.player.faction
if (factionSet.isEmpty()) {
echo.execute("The actor has empty faction set.")
return
}
val count = factionSet.size
echo.execute(count.toString() + Lang.pluralise(" faction", count) + " assigned.")
for (faction in factionSet) {
echo.execute("faction “${faction.factionName}")
echo.execute(" Amicable")
faction.factionAmicable.forEach { s -> echo.execute(PRINT_INDENTATION + s) }
echo.execute(" Explicit neutral")
faction.factionNeutral.forEach { s -> echo.execute(PRINT_INDENTATION + s) }
echo.execute(" Hostile")
faction.factionHostile.forEach { s -> echo.execute(PRINT_INDENTATION + s) }
echo.execute(" Fearful")
faction.factionFearful.forEach { s -> echo.execute(PRINT_INDENTATION + s) }
}
}
}
override fun printUsage() {
}
}

View File

@@ -0,0 +1,24 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-01-22.
*/
class GetLocale : ConsoleCommand {
override fun execute(args: Array<String>) {
Echo().execute(
"Locale: "
+ Lang.get("LANGUAGE_THIS")
+ " ("
+ Lang.get("LANGUAGE_EN")
+ ")")
}
override fun printUsage() {
val echo = Echo()
echo.execute("Usage: getlocale")
echo.execute("Get name of locale currently using.")
}
}

View File

@@ -0,0 +1,21 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-03-20.
*/
class GetTime : ConsoleCommand {
override fun execute(args: Array<String>) {
val echo = Echo()
val worldTime = Terrarum.game.map.worldTime
echo.execute("Year ${worldTime.years}, Month ${worldTime.months}, " +
"Day ${worldTime.days} (${worldTime.getDayNameShort()}), " +
"${worldTime.getFormattedTime()}"
)
}
override fun printUsage() {
Echo().execute("Print current world time in convenient form")
}
}

View File

@@ -0,0 +1,47 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Terrarum
import com.google.gson.Gson
import com.google.gson.JsonElement
import java.io.BufferedWriter
import java.io.FileWriter
import java.io.IOException
/**
* Created by minjaesong on 16-02-10.
*/
class GsonTest : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 2) {
val avelem = Gson().toJsonTree(Terrarum.game.player)
val jsonString = avelem.toString()
val bufferedWriter: BufferedWriter
val writer: FileWriter
try {
writer = FileWriter(Terrarum.defaultDir + "/Exports/" + args[1] + ".json")
bufferedWriter = BufferedWriter(writer)
bufferedWriter.write(jsonString)
bufferedWriter.close()
Echo().execute("GsonTest: exported to " + args[1] + ".json")
}
catch (e: IOException) {
Echo().execute("GsonTest: IOException raised.")
e.printStackTrace()
}
}
else {
printUsage()
}
}
override fun printUsage() {
val echo = Echo()
echo.execute("Usage: gsontest filename-without-extension")
}
}

View File

@@ -0,0 +1,26 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.langpack.Lang
/**
* Created by minjaesong on 16-03-22.
*/
class Help : ConsoleCommand {
override fun execute(args: Array<String>) {
val echo = Echo()
if (args.size == 1) {
for (i in 1..6) echo.execute(Lang["HELP_OTF_MAIN_$i"])
}
else if (args[1].toLowerCase() == "slow") {
for (i in 1..4) echo.execute(Lang["HELP_OTF_SLOW_$i"])
}
else {
for (i in 1..6) echo.execute(Lang["HELP_OTF_MAIN_$i"])
}
}
override fun printUsage() {
Echo().execute("Prints some utility functions assigned to function row of the keyboard.")
}
}

View File

@@ -0,0 +1,15 @@
package net.torvald.terrarum.console
/**
* Created by minjaesong on 16-01-15.
*/
class QuitApp : ConsoleCommand {
override fun execute(args: Array<String>) {
System.exit(1)
}
override fun printUsage() {
}
}

View File

@@ -0,0 +1,61 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Game
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-01-15.
*/
internal class SetAV : ConsoleCommand {
override fun printUsage() {
val echo = Echo()
echo.execute("Set actor value of specific target to desired value.")
echo.execute("Usage: setav (id) <av> <val>")
echo.execute("blank ID for player")
echo.execute("Contaminated (float -> string) actor value will crash the game,")
echo.execute(" so make it sure before you issue the command.")
echo.execute("Use '__true' and '__false' for boolean value.")
}
override fun execute(args: Array<String>) {
val echo = Echo()
// setav <id or "player"> <av> <val>
if (args.size != 4 && args.size != 3) {
printUsage()
}
else if (args.size == 3) {
val `val`: Any
try {
`val` = Integer(args[2]) // try for integer
}
catch (e: NumberFormatException) {
try {
`val` = args[2].toFloat() // try for float
}
catch (ee: NumberFormatException) {
if (args[2].equals("__true", ignoreCase = true)) {
`val` = true
}
else if (args[2].equals("__false", ignoreCase = true)) {
`val` = false
}
else {
`val` = args[2] // string if not number
}
}
}
Terrarum.game.player.actorValue[args[1]] = `val`
echo.execute("Set " + args[1] + " to " + `val`)
}
else if (args.size == 4) {
}
}
}

View File

@@ -0,0 +1,31 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.ui.Notification
/**
* Created by minjaesong on 16-01-23.
*/
class SetBulletin : ConsoleCommand {
override fun execute(args: Array<String>) {
val testMsg = arrayOf(
Lang["ERROR_SAVE_CORRUPTED"],
Lang["MENU_LABEL_CONTINUE_QUESTION"]
)
send(testMsg)
}
override fun printUsage() {
}
/**
* Actually send notifinator
* @param message real message
*/
fun send(message: Array<String>) {
Terrarum.game.sendNotification(message)
println("sent notifinator")
}
}

View File

@@ -0,0 +1,52 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.mapdrawer.LightmapRenderer
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-02-17.
*/
class SetGlobalLightLevel : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 4) {
try {
val r = args[1].toInt()
val g = args[2].toInt()
val b = args[3].toInt()
val GL = LightmapRenderer.constructRGBFromInt(r, g, b)
Terrarum.game.map.globalLight = GL
}
catch (e: NumberFormatException) {
Echo().execute("Wrong number input.")
}
catch (e1: IllegalArgumentException) {
Echo().execute("Range: 0-" + LightmapRenderer.CHANNEL_MAX + " per channel")
}
}
else if (args.size == 2) {
try {
val GL = args[1].toInt()
if (GL.toInt() < 0 || GL.toInt() >= LightmapRenderer.COLOUR_RANGE_SIZE) {
Echo().execute("Range: 0-" + (LightmapRenderer.COLOUR_RANGE_SIZE - 1))
}
else {
Terrarum.game.map.globalLight = GL
}
}
catch (e: NumberFormatException) {
Echo().execute("Wrong number input.")
}
}
else {
printUsage()
}
}
override fun printUsage() {
Echo().execute("Usage: setgl [raw_value|r g b]")
}
}

View File

@@ -0,0 +1,43 @@
package net.torvald.terrarum.console
import net.torvald.imagefont.GameFontBase
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.Terrarum
import org.apache.commons.csv.CSVRecord
import org.newdawn.slick.SlickException
import java.io.IOException
/**
* Created by minjaesong on 16-01-25.
*/
class SetLocale : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 2) {
val prevLocale = Terrarum.gameLocale
Terrarum.gameLocale = args[1]
try {
Echo().execute("Set locale to '" + Terrarum.gameLocale + "'.")
}
catch (e: IOException) {
Echo().execute("could not read lang file.")
Terrarum.gameLocale = prevLocale
}
}
else if (args.size == 1) {
val echo = Echo()
echo.execute("Locales:")
val record = Lang.getRecord("LANGUAGE_ID")
record.forEach { field -> echo.execute("] " + field) }
}
else {
printUsage()
}
}
override fun printUsage() {
Echo().execute("Usage: setlocale [locale]")
}
}

View File

@@ -0,0 +1,42 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.gamemap.WorldTime
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-03-20.
*/
class SetTime : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 2) {
val lowercaseTime = args[1].toLowerCase()
val timeToSet =
if (args[1].length >= 4) {
lowercaseTime.substringBefore('h').toInt() * WorldTime.HOUR_SEC +
lowercaseTime.substringAfter('h').toInt() * WorldTime.MINUTE_SEC
}
else if (args[1].endsWith("h", true)) {
lowercaseTime.substring(0, args[1].length - 1).toInt() * WorldTime.HOUR_SEC
}
else {
lowercaseTime.toInt()
}
Terrarum.game.map.worldTime.setTime(timeToSet)
Echo().execute("Set time to ${Terrarum.game.map.worldTime.elapsedSeconds()} " +
"(${Terrarum.game.map.worldTime.hours}h${formatMin(Terrarum.game.map.worldTime.minutes)})")
}
else {
printUsage()
}
}
private fun formatMin(min: Int): String {
return if (min < 10) "0${min.toString()}" else min.toString()
}
override fun printUsage() {
Echo().execute("usage: settime <39201-in sec or 13h32-in hour>")
}
}

View File

@@ -0,0 +1,25 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-03-20.
*/
class SetTimeDelta : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 2) {
Terrarum.game.map.worldTime.setTimeDelta(args[1].toInt())
if (Terrarum.game.map.worldTime.timeDelta == 0)
Echo().execute("時間よ止まれ!ザ・ワルド!!")
else
Echo().execute("Set time delta to ${Terrarum.game.map.worldTime.timeDelta}")
}
else {
printUsage()
}
}
override fun printUsage() {
Echo().execute("usage: settimedelta <int>")
}
}

View File

@@ -0,0 +1,38 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.gameactors.ActorWithBody
import net.torvald.terrarum.gameactors.PhysTestBall
import net.torvald.terrarum.mapdrawer.MapCamera
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-03-05.
*/
class SpawnPhysTestBall : ConsoleCommand {
@Throws(Exception::class)
override fun execute(args: Array<String>) {
if (args.size == 2) {
val mouseX = Terrarum.appgc.input.mouseX
val mouseY = Terrarum.appgc.input.mouseY
val elasticity = args[1].toFloat()
val ball = PhysTestBall()
ball.setPosition(
(mouseX + MapCamera.cameraX).toFloat(),
(mouseY + MapCamera.cameraY).toFloat()
)
ball.elasticity = elasticity
Terrarum.game.addActor(ball)
}
else {
printUsage()
}
}
override fun printUsage() {
Echo().execute("usage: spawnball [elasticity]")
}
}

View File

@@ -0,0 +1,36 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Game
import net.torvald.terrarum.mapdrawer.MapDrawer
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-01-24.
*/
class TeleportPlayer : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size != 3) {
printUsage()
}
else {
val x: Int
val y: Int
try {
x = args[1].toInt() * MapDrawer.TILE_SIZE + MapDrawer.TILE_SIZE / 2
y = args[2].toInt() * MapDrawer.TILE_SIZE + MapDrawer.TILE_SIZE / 2
}
catch (e: NumberFormatException) {
Echo().execute("Wrong number input.")
return
}
Terrarum.game.player.setPosition(x.toFloat(), y.toFloat())
}
}
override fun printUsage() {
Echo().execute("Usage: teleport [x-tile] [y-tile]")
}
}

View File

@@ -0,0 +1,20 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Game
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-01-19.
*/
class ToggleNoClip : ConsoleCommand {
override fun execute(args: Array<String>) {
val status = Terrarum.game.player.isNoClip()
Terrarum.game.player.setNoClip(!status)
Echo().execute("Set no-clip status to " + (!status).toString())
}
override fun printUsage() {
Echo().execute("toggle no-clip status of player")
}
}

View File

@@ -0,0 +1,42 @@
package net.torvald.terrarum.console
import net.torvald.terrarum.Terrarum
/**
* Created by minjaesong on 16-01-25.
*/
class Zoom : ConsoleCommand {
override fun execute(args: Array<String>) {
if (args.size == 2) {
var zoom: Float
try {
zoom = args[1].toFloat()
}
catch (e: NumberFormatException) {
Echo().execute("Wrong number input.")
return
}
if (zoom < Terrarum.game.ZOOM_MIN) {
zoom = Terrarum.game.ZOOM_MIN
}
else if (zoom > Terrarum.game.ZOOM_MAX) {
zoom = Terrarum.game.ZOOM_MAX
}
Terrarum.game.screenZoom = zoom
System.gc()
Echo().execute("Set screen zoom to " + zoom.toString())
}
else {
printUsage()
}
}
override fun printUsage() {
Echo().execute("Usage: zoom [zoom]")
}
}

View File

@@ -0,0 +1,10 @@
package net.torvald.terrarum.gameactors
import net.torvald.terrarum.gameactors.ai.ActorAI
/**
* Created by minjaesong on 16-03-14.
*/
interface AIControlled {
fun attachAI(ai: ActorAI)
}

View File

@@ -0,0 +1,34 @@
package net.torvald.terrarum.gameactors
/**
* Created by minjaesong on 16-04-02.
*/
object AVKey {
const val MULT = "mult"
const val SPEED = "speed"
const val SPEEDMULT = "speed$MULT"
const val ACCEL = "accel"
const val ACCELMULT = "accel$MULT"
const val SCALE = "scale"
const val BASEHEIGHT = "baseheight"
const val BASEMASS = "basemass"
const val JUMPPOWER = "jumppower"
const val JUMPPOWERMULT = "jumppower$MULT"
const val STRENGTH = "strength"
const val ENCUMBRANCE = "encumbrance"
const val LUMINOSITY = "luminosity"
const val PHYSIQUEMULT = "physique$MULT"
const val NAME = "name"
const val RACENAME = "racename"
const val RACENAMEPLURAL = "racenameplural"
const val TOOLSIZE = "toolsize"
const val INTELLIGENT = "intelligent"
const val BASEDEFENCE = "basedefence" // creature base
const val ARMOURDEFENCE = "armourdefence" // armour points
const val ARMOURDEFENCEMULT = "armourdefence$MULT"
}

View File

@@ -0,0 +1,19 @@
package net.torvald.terrarum.gameactors
import org.newdawn.slick.GameContainer
/**
* Created by minjaesong on 16-03-14.
*/
interface Actor {
fun update(gc: GameContainer, delta_t: Int)
/**
* Valid RefID is equal to or greater than 32768.
* @return Reference ID. (32768-0x7FFF_FFFF_FFFF_FFFF)
*/
var referenceID: Long
var actorValue: ActorValue
}

View File

@@ -0,0 +1,146 @@
package net.torvald.terrarum.gameactors
import net.torvald.terrarum.gameitem.InventoryItem
import net.torvald.terrarum.itemproperties.ItemPropCodex
import java.util.*
/**
* Created by minjaesong on 16-03-15.
*/
class ActorInventory() {
@Transient val CAPACITY_MAX = 0x7FFFFFFF
@Transient val CAPACITY_MODE_NO_ENCUMBER = 0
@Transient val CAPACITY_MODE_COUNT = 1
@Transient val CAPACITY_MODE_WEIGHT = 2
private var capacityByCount: Int
private var capacityByWeight: Int
private var capacityMode: Int
/**
* &lt;ReferenceID, Amounts&gt;
*/
private val itemList: HashMap<Long, Int> = HashMap()
/**
* Default constructor with no encumbrance.
*/
init {
capacityMode = CAPACITY_MODE_NO_ENCUMBER
capacityByCount = 0
capacityByWeight = 0
}
/**
* Construct new inventory with specified capacity.
* @param capacity if is_weight is true, killogramme value is required, counts of items otherwise.
* *
* @param is_weight whether encumbrance should be calculated upon the weight of the inventory. False to use item counts.
*/
constructor(capacity: Int, is_weight: Boolean) : this() {
if (is_weight) {
capacityByWeight = capacity
capacityMode = CAPACITY_MODE_WEIGHT
} else {
capacityByCount = capacity
capacityMode = CAPACITY_MODE_COUNT
}
}
/**
* Get capacity of inventory
* @return
*/
fun getCapacity(): Int {
if (capacityMode == CAPACITY_MODE_NO_ENCUMBER) {
return CAPACITY_MAX
}
else if (capacityMode == CAPACITY_MODE_WEIGHT) {
return capacityByWeight
}
else {
return capacityByCount
}
}
fun getCapacityMode(): Int {
return capacityMode
}
/**
* Get reference to the itemList
* @return
*/
fun getItemList(): Map<Long, Int>? {
return itemList
}
/**
* Get clone of the itemList
* @return
*/
@Suppress("UNCHECKED_CAST")
fun getCopyOfItemList(): Map<Long, Int>? {
return itemList.clone() as Map<Long, Int>
}
fun getTotalWeight(): Float {
var weight = 0f
for (item in itemList.entries) {
weight += ItemPropCodex.getItem(item.key).mass * item.value
}
return weight
}
fun getTotalCount(): Int {
var count = 0
for (item in itemList.entries) {
count += item.value
}
return count
}
fun getTotalUniqueCount(): Int {
return itemList.entries.size
}
fun appendToPocket(item: InventoryItem) {
appendToPocket(item, 1)
}
fun appendToPocket(item: InventoryItem, count: Int) {
val key = item.itemID
// if (key == Player.PLAYER_REF_ID)
// throw new IllegalArgumentException("Attempted to put player into the inventory.");
if (itemList.containsKey(key))
// increment amount if it already has specified item
itemList.put(key, itemList[key]!! + count)
else
// add new entry if it does not have specified item
itemList.put(key, count)
}
/**
* Check whether the itemList contains too many items
* @return
*/
fun isEncumbered(): Boolean {
if (getCapacityMode() == CAPACITY_MODE_WEIGHT) {
return capacityByWeight < getTotalWeight()
} else if (getCapacityMode() == CAPACITY_MODE_COUNT) {
return capacityByCount < getTotalWeight()
} else {
return false
}
}
}

View File

@@ -0,0 +1,8 @@
package net.torvald.terrarum.gameactors
import net.torvald.terrarum.KVHashMap
/**
* Created by minjaesong on 16-03-19.
*/
class ActorValue : KVHashMap()

View File

@@ -0,0 +1,779 @@
package net.torvald.terrarum.gameactors
import net.torvald.random.HQRNG
import net.torvald.terrarum.*
import net.torvald.terrarum.gamemap.GameMap
import net.torvald.terrarum.mapdrawer.MapDrawer
import net.torvald.terrarum.tileproperties.TilePropCodex
import net.torvald.spriteanimation.SpriteAnimation
import com.jme3.math.FastMath
import net.torvald.terrarum.tileproperties.TileNameCode
import org.newdawn.slick.GameContainer
import org.newdawn.slick.Graphics
/**
* Base class for every actor that has physical (or visible) body. This includes furnishings, paintings, gadgets, etc.
*
* Created by minjaesong on 16-03-14.
*/
open class ActorWithBody constructor() : Actor, Visible, Glowing {
override var actorValue: ActorValue = ActorValue()
var hitboxTranslateX: Float = 0.toFloat()// relative to spritePosX
var hitboxTranslateY: Float = 0.toFloat()// relative to spritePosY
var baseHitboxW: Int = 0
var baseHitboxH: Int = 0
/**
* Velocity for newtonian sim.
* Fluctuation in, otherwise still, velocity is equal to acceleration.
* Acceleration: used in code like:
* veloY += 3.0
* +3.0 is acceleration. You __accumulate__ acceleration to the velocity.
*/
@Volatile var veloX: Float = 0.toFloat()
@Volatile var veloY: Float = 0.toFloat()
@Transient private val VELO_HARD_LIMIT = 10000f
var grounded = false
@Transient var sprite: SpriteAnimation? = null
@Transient var spriteGlow: SpriteAnimation? = null
/** Default to 'false' */
var isVisible = false
/** Default to 'true' */
var isUpdate = true
var isNoSubjectToGrav = false
var isNoCollideWorld = false
var isNoSubjectToFluidResistance = false
internal var baseSpriteWidth: Int = 0
internal var baseSpriteHeight: Int = 0
override var referenceID: Long = 0L
/**
* Positions: top-left point
*/
val hitbox = Hitbox(0f,0f,0f,0f)
@Transient val nextHitbox = Hitbox(0f,0f,0f,0f)
/**
* Physical properties
*/
@Volatile @Transient var scale = 1f
@Volatile @Transient var mass = 2f
@Transient private val MASS_LOWEST = 2f
/** Valid range: [0, 1] */
var elasticity = 0f
set(value) {
if (value < 0)
throw IllegalArgumentException("[ActorWithBody] $value: valid elasticity value is [0, 1].")
else if (value > 1) {
println("[ActorWithBody] Elasticity were capped to 1.")
field = ELASTICITY_MAX
}
else
field = value * ELASTICITY_MAX
}
@Transient private val ELASTICITY_MAX = 0.993f
private var density = 1000f
/**
* Gravitational Constant G. Load from gamemap.
* [m / s^2]
* s^2 = 1/FPS = 1/60 if FPS is targeted to 60
* meter to pixel : 24/FPS
*/
@Transient private val METER = 24f
/**
* [m / s^2] * SI_TO_GAME_ACC -> [px / IFrame^2]
*/
@Transient private val SI_TO_GAME_ACC = METER / FastMath.sqr(Terrarum.TARGET_FPS.toFloat())
/**
* [m / s] * SI_TO_GAME_VEL -> [px / IFrame]
*/
@Transient private val SI_TO_GAME_VEL = METER / Terrarum.TARGET_FPS
@Transient private var gravitation: Float = 0.toFloat()
@Transient private val DRAG_COEFF = 1f
@Transient private val CONTACT_AREA_TOP = 0
@Transient private val CONTACT_AREA_RIGHT = 1
@Transient private val CONTACT_AREA_BOTTOM = 2
@Transient private val CONTACT_AREA_LEFT = 3
@Transient private val UD_COMPENSATOR_MAX = TSIZE
@Transient private val LR_COMPENSATOR_MAX = TSIZE
@Transient private val TILE_AUTOCLIMB_RATE = 4
/**
* A constant to make falling faster so that the game is more playable
*/
@Transient private val G_MUL_PLAYABLE_CONST = 1.4142f
@Transient private val EVENT_MOVE_TOP = 0
@Transient private val EVENT_MOVE_RIGHT = 1
@Transient private val EVENT_MOVE_BOTTOM = 2
@Transient private val EVENT_MOVE_LEFT = 3
@Transient private val EVENT_MOVE_NONE = -1
@Transient internal var eventMoving = EVENT_MOVE_NONE // cannot collide both X-axis and Y-axis, or else jump control breaks up.
/**
* in milliseconds
*/
@Transient val INVINCIBILITY_TIME = 500
@Transient private val map: GameMap
@Transient private val MASS_DEFAULT = 60f
private var posAdjustX = 0
private var posAdjustY = 0
init {
do {
referenceID = HQRNG().nextLong() // set new ID
} while (Terrarum.game.hasActor(referenceID)) // check for collision
map = Terrarum.game.map
}
/**
* @param w
* *
* @param h
* *
* @param tx +: translate drawn sprite to LEFT.
* *
* @param ty +: translate drawn sprite to DOWN.
* *
* @see ActorWithBody.drawBody
* @see ActorWithBody.drawGlow
*/
fun setHitboxDimension(w: Int, h: Int, tx: Int, ty: Int) {
baseHitboxH = h
baseHitboxW = w
hitboxTranslateX = tx.toFloat()
hitboxTranslateY = ty.toFloat()
}
/**
* Set hitbox position from bottom-center point
* @param x
* *
* @param y
*/
fun setPosition(x: Float, y: Float) {
hitbox.set(
x - (baseHitboxW / 2 - hitboxTranslateX) * scale,
y - (baseHitboxH - hitboxTranslateY) * scale,
baseHitboxW * scale,
baseHitboxH * scale)
nextHitbox.set(
x - (baseHitboxW / 2 - hitboxTranslateX) * scale,
y - (baseHitboxH - hitboxTranslateY) * scale,
baseHitboxW * scale,
baseHitboxH * scale)
}
private fun updatePhysicalInfos() {
scale = actorValue.getAsFloat(AVKey.SCALE) ?: 1f
mass = (actorValue.getAsFloat(AVKey.BASEMASS) ?: MASS_DEFAULT) * FastMath.pow(scale, 3f)
if (elasticity != 0f) elasticity = 0f
}
override fun update(gc: GameContainer, delta_t: Int) {
if (isUpdate) {
updatePhysicalInfos()
/**
* Update variables
*/
// make NoClip work for player
if (this is Player) {
isNoSubjectToGrav = isPlayerNoClip
isNoCollideWorld = isPlayerNoClip
isNoSubjectToFluidResistance = isPlayerNoClip
}
// clamp to the minimum possible mass
if (mass < MASS_LOWEST) mass = MASS_LOWEST
// set sprite dimension vars if there IS sprite for the actor
if (sprite != null) {
baseSpriteHeight = sprite!!.height
baseSpriteWidth = sprite!!.width
}
// copy gravitational constant from the map the actor is in
gravitation = map.gravitation
// Auto climb rate. Clamp to TSIZE
AUTO_CLIMB_RATE = Math.min(TSIZE / 8 * FastMath.sqrt(scale), TSIZE.toFloat()).toInt()
// Actors are subject to the gravity and the buoyancy if they are not levitating
if (!isNoSubjectToGrav) {
applyGravitation()
//applyBuoyancy()
}
// hard limit velocity
if (veloX > VELO_HARD_LIMIT) veloX = VELO_HARD_LIMIT
if (veloY > VELO_HARD_LIMIT) veloY = VELO_HARD_LIMIT
// Set 'next' position (hitbox) to fiddle with
updateNextHitboxFromVelo()
// if not horizontally moving then ...
//if (Math.abs(veloX) < 0.5) { // fix for special situations (see fig. 1 at the bottom of the source)
// updateVerticalPos();
// updateHorizontalPos();
//}
//else {
// compensate for colliding
updateHorizontalPos()
updateVerticalPos()
//}
// apply our compensation to actual hitbox
updateHitboxX()
updateHitboxY()
// make sure the actor does not go out of the map
clampNextHitbox()
clampHitbox()
}
}
/**
* Apply gravitation to the every falling body (unless not levitating)
* Apply only if not grounded; normal force is not implemented (and redundant)
* so we manually reset G to zero (not applying G. force) if grounded.
*/
// FIXME abnormal jump behaviour if mass < 2, same thing happens if mass == 0 (but zero mass is invalid anyway).
private fun applyGravitation() {
if (!grounded) {
/**
* weight; gravitational force in action
* W = mass * G (9.8 [m/s^2])
*/
val W = gravitation * mass
/**
* Drag of atmosphere
* D = Cd (drag coefficient) * 0.5 * rho (density) * V^2 (velocity) * A (area)
*/
val A = scale * scale
val D = DRAG_COEFF * 0.5f * 1.292f * veloY * veloY * A
veloY += clampCeil(
(W - D) / mass * SI_TO_GAME_ACC * G_MUL_PLAYABLE_CONST, VELO_HARD_LIMIT)
}
}
private fun updateVerticalPos() {
if (!isNoCollideWorld) {
if (veloY >= 0) { // check downward
if (isColliding(CONTACT_AREA_BOTTOM)) { // the ground has dug into the body
adjustHitBottom()
veloY = 0f // reset veloY, simulating normal force
elasticReflectY()
grounded = true
}
else if (isColliding(CONTACT_AREA_BOTTOM, 0, 1)) { // the actor is standing ON the ground
veloY = 0f // reset veloY, simulating normal force
elasticReflectY()
grounded = true
}
else { // the actor is not grounded at all
grounded = false
}
}
else if (veloY < 0) { // check upward
grounded = false
if (isColliding(CONTACT_AREA_TOP)) { // the ceiling has dug into the body
adjustHitTop()
veloY = 0f // reset veloY, simulating normal force
elasticReflectY()
}
else if (isColliding(CONTACT_AREA_TOP, 0, -1)) { // the actor is touching the ceiling
veloY = 0f // reset veloY, simulating normal force
elasticReflectY() // reflect on ceiling, for reversed gravity
}
else { // the actor is not grounded at all
}
}
}
}
private fun adjustHitBottom() {
val newX = nextHitbox.pointedX // look carefully, getPos or getPointed
// int-ify posY of nextHitbox
nextHitbox.setPositionYFromPoint(FastMath.floor(nextHitbox.pointedY).toFloat())
var newYOff = 0 // always positive
// count up Y offset until the actor is not touching the ground
var colliding: Boolean
do {
newYOff += 1
colliding = isColliding(CONTACT_AREA_BOTTOM, 0, -newYOff)
} while (colliding)
posAdjustY = -newYOff
val newY = nextHitbox.pointedY - newYOff
nextHitbox.setPositionFromPoint(newX, newY)
}
private fun adjustHitTop() {
val newX = nextHitbox.posX
// int-ify posY of nextHitbox
nextHitbox.setPositionY(FastMath.ceil(nextHitbox.posY).toFloat())
var newYOff = 0 // always positive
// count up Y offset until the actor is not touching the ceiling
var colliding: Boolean
do {
newYOff += 1
colliding = isColliding(CONTACT_AREA_TOP, 0, newYOff)
} while (colliding)
posAdjustY = newYOff
val newY = nextHitbox.posY + newYOff
nextHitbox.setPosition(newX, newY)
}
private fun updateHorizontalPos() {
if (!isNoCollideWorld) {
if (veloX >= 0.5) { // check right
if (isColliding(CONTACT_AREA_RIGHT) && !isColliding(CONTACT_AREA_LEFT)) {
// the actor is embedded to the wall
adjustHitRight()
veloX = 0f // reset veloX, simulating normal force
elasticReflectX()
}
else if (isColliding(CONTACT_AREA_RIGHT, 2, 0) && !isColliding(CONTACT_AREA_LEFT, 0, 0)) { // offset by +1, to fix directional quarks
// the actor is touching the wall
veloX = 0f // reset veloX, simulating normal force
elasticReflectX()
}
else {
}
}
else if (veloX <= -0.5) { // check left
// System.out.println("collidingleft");
if (isColliding(CONTACT_AREA_LEFT) && !isColliding(CONTACT_AREA_RIGHT)) {
// the actor is embedded to the wall
adjustHitLeft()
veloX = 0f // reset veloX, simulating normal force
elasticReflectX()
}
else if (isColliding(CONTACT_AREA_LEFT, -1, 0) && !isColliding(CONTACT_AREA_RIGHT, 1, 0)) {
// the actor is touching the wall
veloX = 0f // reset veloX, simulating normal force
elasticReflectX()
}
else {
}
}
else { // check both sides?
// System.out.println("updatehorizontal - |velo| < 0.5");
//if (isColliding(CONTACT_AREA_LEFT) || isColliding(CONTACT_AREA_RIGHT)) {
// veloX = 0f // reset veloX, simulating normal force
// elasticReflectX()
//}
}
}
}
private fun adjustHitRight() {
val newY = nextHitbox.posY // look carefully, posY or pointedY
// int-ify posY of nextHitbox
nextHitbox.setPositionX(FastMath.floor(nextHitbox.posX + nextHitbox.width) - nextHitbox.width)
var newXOff = 0 // always positive
// count up Y offset until the actor is not touching the wall
var colliding: Boolean
do {
newXOff += 1
colliding = isColliding(CONTACT_AREA_BOTTOM, -newXOff + 1, 0) // offset by +1, to fix directional quarks
} while (newXOff < TSIZE && colliding)
val newX = nextHitbox.posX - newXOff // -1: Q&D way to prevent the actor sticking to the wall and won't detach
nextHitbox.setPosition(newX, newY)
}
private fun adjustHitLeft() {
val newY = nextHitbox.posY
// int-ify posY of nextHitbox
nextHitbox.setPositionX(FastMath.ceil(nextHitbox.posX).toFloat())
var newXOff = 0 // always positive
// count up Y offset until the actor is not touching the wall
var colliding: Boolean
do {
newXOff += 1
colliding = isColliding(CONTACT_AREA_TOP, newXOff, 0)
} while (newXOff < TSIZE && colliding)
posAdjustX = newXOff
val newX = nextHitbox.posX + newXOff // +1: Q&D way to prevent the actor sticking to the wall and won't detach
nextHitbox.setPosition(newX, newY)
}
private fun elasticReflectX() {
if (veloX != 0f && (veloX * elasticity).abs() > 0.5) {
veloX = -veloX * elasticity
}
}
private fun elasticReflectY() {
if (veloY != 0f && (veloY * elasticity).abs() > 0.5) {
veloY = -veloY * elasticity
}
}
private fun isColliding(side: Int, tx: Int = 0, ty: Int = 0): Boolean = getContactingArea(side, tx, ty) > 1
private fun getContactingArea(side: Int, translateX: Int = 0, translateY: Int = 0): Int {
var contactAreaCounter = 0
for (i in 0..(if (side % 2 == 0) nextHitbox.width else nextHitbox.height).roundToInt() - 1) {
// set tile positions
val tileX: Int
val tileY: Int
if (side == CONTACT_AREA_BOTTOM) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxEnd.y.roundToInt() + translateY)
}
else if (side == CONTACT_AREA_TOP) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt() + translateY)
}
else if (side == CONTACT_AREA_RIGHT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxEnd.x.roundToInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt()
+ i + translateY)
}
else if (side == CONTACT_AREA_LEFT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt()
+ i + translateY)
}
else {
throw IllegalArgumentException(side.toString() + ": Wrong side input")
}
// evaluate
if (TilePropCodex.getProp(map.getTileFromTerrain(tileX, tileY) ?: TileNameCode.STONE).isSolid) {
contactAreaCounter += 1
}
}
return contactAreaCounter
}
private fun getContactingAreaFluid(side: Int, translateX: Int = 0, translateY: Int = 0): Int {
var contactAreaCounter = 0
for (i in 0..(if (side % 2 == 0) nextHitbox.width else nextHitbox.height).roundToInt() - 1) {
// set tile positions
val tileX: Int
val tileY: Int
if (side == CONTACT_AREA_BOTTOM) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxEnd.y.roundToInt() + translateY)
}
else if (side == CONTACT_AREA_TOP) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt() + translateY)
}
else if (side == CONTACT_AREA_RIGHT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxEnd.x.roundToInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt()
+ i + translateY)
}
else if (side == CONTACT_AREA_LEFT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt()
+ i + translateY)
}
else {
throw IllegalArgumentException(side.toString() + ": Wrong side input")
}
// evaluate
if (TilePropCodex.getProp(map.getTileFromTerrain(tileX, tileY)).isFluid) {
contactAreaCounter += 1
}
}
return contactAreaCounter
}
/**
* [N] = [kg * m / s^2]
* F(bo) = density * submerged_volume * gravitational_acceleration [N]
*/
/*private fun applyBuoyancy() {
val fluidDensity = tileDensity
val submergedVolume = submergedVolume
if (!isPlayerNoClip && !grounded) {
// System.out.println("density: "+density);
veloY -= ((fluidDensity - this.density).toDouble()
* map.gravitation.toDouble() * submergedVolume.toDouble()
* Math.pow(mass.toDouble(), -1.0)
* SI_TO_GAME_ACC.toDouble()).toFloat()
}
}*/
/*private val submergedVolume: Float
get() = submergedHeight * hitbox.width * hitbox.width
private val submergedHeight: Float
get() = Math.max(
getContactingAreaFluid(CONTACT_AREA_LEFT),
getContactingAreaFluid(CONTACT_AREA_RIGHT)
).toFloat()*/
/**
* Get highest friction value from feet tiles.
* @return
*/
private val tileFriction: Int
get() {
var friction = 0
//get highest fluid density
val tilePosXStart = (nextHitbox.posX / TSIZE).roundToInt()
val tilePosXEnd = (nextHitbox.hitboxEnd.x / TSIZE).roundToInt()
val tilePosY = (nextHitbox.pointedY / TSIZE).roundToInt()
for (x in tilePosXStart..tilePosXEnd) {
val tile = map.getTileFromTerrain(x, tilePosY)
if (TilePropCodex.getProp(tile).isFluid) {
val thisFluidDensity = TilePropCodex.getProp(tile).friction
if (thisFluidDensity > friction) friction = thisFluidDensity
}
}
return friction
}
/**
* Get highest density (specific gravity) value from tiles that the body occupies.
* @return
*/
private val tileDensity: Int
get() {
var density = 0
//get highest fluid density
val tilePosXStart = (nextHitbox.posX / TSIZE).roundToInt()
val tilePosYStart = (nextHitbox.posY / TSIZE).roundToInt()
val tilePosXEnd = (nextHitbox.hitboxEnd.x / TSIZE).roundToInt()
val tilePosYEnd = (nextHitbox.hitboxEnd.y / TSIZE).roundToInt()
for (y in tilePosYStart..tilePosYEnd) {
for (x in tilePosXStart..tilePosXEnd) {
val tile = map.getTileFromTerrain(x, y)
if (TilePropCodex.getProp(tile).isFluid) {
val thisFluidDensity = TilePropCodex.getProp(tile).density
if (thisFluidDensity > density) density = thisFluidDensity
}
}
}
return density
}
private fun mvmtRstcToMultiplier(movementResistanceValue: Int): Float {
return 1f / (1 + movementResistanceValue / 16f)
}
private fun clampHitbox() {
hitbox.setPositionFromPoint(
clampW(hitbox.pointedX), clampH(hitbox.pointedY))
}
private fun clampNextHitbox() {
nextHitbox.setPositionFromPoint(
clampW(nextHitbox.pointedX), clampH(nextHitbox.pointedY))
}
private fun updateNextHitboxFromVelo() {
nextHitbox.set(
(hitbox.posX + veloX).round()
, (hitbox.posY + veloY).round()
, (baseHitboxW * scale).round()
, (baseHitboxH * scale).round()
)
/** Full quantisation; wonder what havoc these statements would wreak...
*/
}
private fun updateHitboxX() {
hitbox.setDimension(
nextHitbox.width, nextHitbox.height)
hitbox.setPositionX(nextHitbox.posX)
}
private fun updateHitboxY() {
hitbox.setDimension(
nextHitbox.width, nextHitbox.height)
hitbox.setPositionY(nextHitbox.posY)
}
override fun drawGlow(gc: GameContainer, g: Graphics) {
if (isVisible && spriteGlow != null) {
if (!sprite!!.flippedHorizontal()) {
spriteGlow!!.render(g, hitbox.posX - hitboxTranslateX * scale, hitbox.posY + hitboxTranslateY * scale - (baseSpriteHeight - baseHitboxH) * scale + 2, scale)
} else {
spriteGlow!!.render(g, hitbox.posX - scale, hitbox.posY + hitboxTranslateY * scale - (baseSpriteHeight - baseHitboxH) * scale + 2, scale)
}
}
}
override fun drawBody(gc: GameContainer, g: Graphics) {
if (isVisible && sprite != null) {
if (!sprite!!.flippedHorizontal()) {
sprite!!.render(g, hitbox.posX - hitboxTranslateX * scale, hitbox.posY + hitboxTranslateY * scale - (baseSpriteHeight - baseHitboxH) * scale + 2, scale)
} else {
sprite!!.render(g, hitbox.posX - scale, hitbox.posY + hitboxTranslateY * scale - (baseSpriteHeight - baseHitboxH) * scale + 2, scale)
}
}
}
override fun updateGlowSprite(gc: GameContainer, delta: Int) {
if (spriteGlow != null) spriteGlow!!.update(delta)
}
override fun updateBodySprite(gc: GameContainer, delta: Int) {
if (sprite != null) sprite!!.update(delta)
}
private fun clampW(x: Float): Float =
if (x < TSIZE + nextHitbox.width / 2) {
TSIZE + nextHitbox.width / 2
} else if (x >= (map.width * TSIZE).toFloat() - TSIZE.toFloat() - nextHitbox.width / 2) {
(map.width * TSIZE).toFloat() - 1f - TSIZE.toFloat() - nextHitbox.width / 2
} else {
x
}
private fun clampH(y: Float): Float =
if (y < TSIZE + nextHitbox.height) {
TSIZE + nextHitbox.height
} else if (y >= (map.height * TSIZE).toFloat() - TSIZE.toFloat() - nextHitbox.height) {
(map.height * TSIZE).toFloat() - 1f - TSIZE.toFloat() - nextHitbox.height
} else {
y
}
private fun clampWtile(x: Int): Int =
if (x < 0) {
0
} else if (x >= map.width) {
map.width - 1
} else {
x
}
private fun clampHtile(x: Int): Int =
if (x < 0) {
0
} else if (x >= map.height) {
map.height - 1
} else {
x
}
private val isPlayerNoClip: Boolean
get() = this is Player && this.isNoClip()
private fun quantiseTSize(v: Float): Int = FastMath.floor(v / TSIZE) * TSIZE
fun setDensity(density: Int) {
if (density < 0)
throw IllegalArgumentException("[ActorWithBody] $density: density cannot be negative.")
this.density = density.toFloat()
}
fun Float.round() = Math.round(this).toFloat()
fun Float.roundToInt(): Int = Math.round(this)
fun Float.abs() = FastMath.abs(this)
companion object {
@Transient private val TSIZE = MapDrawer.TILE_SIZE
private var AUTO_CLIMB_RATE = TSIZE / 8
private fun div16(x: Int): Int {
if (x < 0) {
throw IllegalArgumentException("div16: Positive integer only: " + x.toString())
}
return x and 0x7FFFFFFF shr 4
}
private fun div16TruncateToMapWidth(x: Int): Int {
if (x < 0)
return 0
else if (x >= Terrarum.game.map.width shl 4)
return Terrarum.game.map.width - 1
else
return x and 0x7FFFFFFF shr 4
}
private fun div16TruncateToMapHeight(y: Int): Int {
if (y < 0)
return 0
else if (y >= Terrarum.game.map.height shl 4)
return Terrarum.game.map.height - 1
else
return y and 0x7FFFFFFF shr 4
}
private fun mod16(x: Int): Int {
if (x < 0) {
throw IllegalArgumentException("mod16: Positive integer only: " + x.toString())
}
return x and 15
}
private fun clampCeil(x: Float, ceil: Float): Float {
return if (Math.abs(x) > ceil) ceil else x
}
}
}
/**
* Give new random ReferenceID and initialise ActorValue
*/
/**
= = ↑
=== ===@!
=↑ =↑
=↑ =
=↑ =
=@ (pressing R) =
================== ==================
Fig. 1: the fix was not applied
*/

View File

@@ -0,0 +1,20 @@
package net.torvald.terrarum.gameactors
import net.torvald.terrarum.gameitem.InventoryItem
/**
* Created by minjaesong on 16-03-14.
*/
interface CanBeAnItem {
fun attachItemData()
fun getItemWeight(): Float
fun stopUpdateAndDraw()
fun resumeUpdateAndDraw()
var itemData: InventoryItem
}

View File

@@ -0,0 +1,14 @@
package net.torvald.terrarum.gameactors
import org.newdawn.slick.Input
/**
* Created by minjaesong on 16-03-14.
*/
interface Controllable {
fun processInput(input: Input)
fun keyPressed(key: Int, c: Char)
}

View File

@@ -0,0 +1,27 @@
package net.torvald.terrarum.gameactors
import net.torvald.JsonFetcher
import net.torvald.random.Fudge3
import net.torvald.random.HQRNG
import net.torvald.terrarum.langpack.Lang
import com.google.gson.JsonObject
import org.newdawn.slick.SlickException
import java.io.IOException
/**
* Created by minjaesong on 16-03-14.
*/
object CreatureBuilder {
/**
* @Param jsonFileName with extension
*/
@Throws(IOException::class, SlickException::class)
fun create(jsonFileName: String): ActorWithBody {
val actor = ActorWithBody()
CreatureRawInjector.inject(actor.actorValue, jsonFileName)
return actor
}
}

View File

@@ -0,0 +1,123 @@
package net.torvald.terrarum.gameactors
import net.torvald.JsonFetcher
import net.torvald.random.Fudge3
import net.torvald.terrarum.langpack.Lang
import com.google.gson.JsonObject
import org.newdawn.slick.SlickException
import java.io.IOException
import java.security.SecureRandom
/**
* Created by minjaesong on 16-03-25.
*/
object CreatureRawInjector {
const val JSONPATH = "./res/raw/creatures/"
private const val MULTIPLIER_RAW_ELEM_SUFFIX = AVKey.MULT
/**
* 'Injects' creature raw ActorValue to the ActorValue reference provided.
*
* @param actorValueRef ActorValue object to be injected.
* @param jsonFileName with extension
*/
@Throws(IOException::class, SlickException::class)
fun inject(actorValueRef: ActorValue, jsonFileName: String) {
val jsonObj = JsonFetcher.readJson(JSONPATH + jsonFileName)
val elementsString = arrayOf(AVKey.RACENAME, AVKey.RACENAMEPLURAL)
val elementsFloat = arrayOf(AVKey.BASEHEIGHT, AVKey.BASEMASS, AVKey.ACCEL, AVKey.TOOLSIZE, AVKey.ENCUMBRANCE)
val elementsFloatVariable = arrayOf(AVKey.STRENGTH, AVKey.SPEED, AVKey.JUMPPOWER, AVKey.SCALE, AVKey.SPEED)
val elementsBoolean = arrayOf(AVKey.INTELLIGENT)
// val elementsMultiplyFromOne = arrayOf()
setAVStrings(actorValueRef, elementsString, jsonObj)
setAVFloats(actorValueRef, elementsFloat, jsonObj)
setAVFloatsVariable(actorValueRef, elementsFloatVariable, jsonObj)
// setAVMultiplyFromOne(actorValueRef, elementsMultiplyFromOne, jsonObj)
setAVBooleans(actorValueRef, elementsBoolean, jsonObj)
actorValueRef[AVKey.ACCEL] = Player.WALK_ACCEL_BASE
actorValueRef[AVKey.ACCELMULT] = 1f
}
/**
* Fetch and set actor values that have 'variable' appended. E.g. strength
* @param avRef
* *
* @param elemSet
* *
* @param jsonObject
*/
private fun setAVFloatsVariable(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
for (s in elemSet) {
val baseValue = jsonObject.get(s).asFloat
// roll fudge dice and get value [-3, 3] as [0, 6]
val varSelected = Fudge3(SecureRandom()).rollForArray()
// get multiplier from json. Assuming percentile
val multiplier = jsonObject.get(s + MULTIPLIER_RAW_ELEM_SUFFIX).asJsonArray.get(varSelected).asInt
val realValue = baseValue * multiplier / 100f
avRef[s] = realValue
avRef[s + MULTIPLIER_RAW_ELEM_SUFFIX] = 1.0f // use multiplied value as 'base' for all sort of things
}
}
/**
* Fetch and set string actor values
* @param avRef
* *
* @param elemSet
* *
* @param jsonObject
*/
private fun setAVStrings(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
for (s in elemSet) {
val key = jsonObject.get(s).asString
avRef[s] = Lang[key]
}
}
/**
* Fetch and set float actor values
* @param avRef
* *
* @param elemSet
* *
* @param jsonObject
*/
private fun setAVFloats(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
for (s in elemSet) {
avRef[s] = jsonObject.get(s).asFloat
}
}
/**
* Fetch and set actor values that should multiplier be applied to the base value of 1.
* E.g. physiquemult
* @param avRef
* *
* @param elemSet
* *
* @param jsonObject
*/
private fun setAVMultiplyFromOne(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
for (s in elemSet) {
val baseValue = 1f
// roll fudge dice and get value [-3, 3] as [0, 6]
val varSelected = Fudge3(SecureRandom()).rollForArray()
// get multiplier from json. Assuming percentile
val multiplier = jsonObject.get(s).asJsonArray.get(varSelected).asInt
val realValue = baseValue * multiplier / 100f
avRef[s] = realValue
}
}
private fun setAVBooleans(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
for (s in elemSet) {
avRef[s] = jsonObject.get(s).asBoolean
}
}
}

View File

@@ -0,0 +1,22 @@
package net.torvald.terrarum.gameactors
import org.newdawn.slick.GameContainer
import org.newdawn.slick.Graphics
/**
* Created by minjaesong on 16-03-15.
*/
class DroppedItem constructor() : ActorWithBody() {
init {
isVisible = true
}
override fun update(gc: GameContainer, delta_t: Int) {
}
override fun drawBody(gc: GameContainer, g: Graphics) {
drawBody(gc, g)
}
}

View File

@@ -0,0 +1,13 @@
package net.torvald.terrarum.gameactors
import net.torvald.terrarum.gameactors.faction.Faction
import java.util.*
/**
* Created by minjaesong on 16-03-14.
*/
interface Factionable {
var faction: HashSet<Faction>
}

View File

@@ -0,0 +1,13 @@
package net.torvald.terrarum.gameactors
import org.newdawn.slick.GameContainer
import org.newdawn.slick.Graphics
/**
* Created by minjaesong on 16-03-14.
*/
interface Glowing {
fun drawGlow(gc: GameContainer, g: Graphics)
fun updateGlowSprite(gc: GameContainer, delta: Int)
}

View File

@@ -0,0 +1,115 @@
package net.torvald.terrarum.gameactors
import net.torvald.point.Point2f
/**
* Created by minjaesong on 16-01-15.
*/
class Hitbox(x1: Float, y1: Float, width: Float, height: Float) {
var hitboxStart: Point2f
private set
var hitboxEnd: Point2f
private set
var width: Float = 0.toFloat()
private set
var height: Float = 0.toFloat()
private set
init {
hitboxStart = Point2f(x1, y1)
hitboxEnd = Point2f(x1 + width, y1 + height)
this.width = width
this.height = height
}
/**
* Returns bottom-centered point of hitbox.
* @return pointX
*/
val pointedX: Float
get() = hitboxStart.x + width / 2
/**
* Returns bottom-centered point of hitbox.
* @return pointY
*/
val pointedY: Float
get() = hitboxEnd.y
/**
* Set to the point top left
* @param x1
* *
* @param y1
* *
* @param width
* *
* @param height
*/
operator fun set(x1: Float, y1: Float, width: Float, height: Float) {
hitboxStart = Point2f(x1, y1)
hitboxEnd = Point2f(x1 + width, y1 + height)
this.width = width
this.height = height
}
fun setPosition(x1: Float, y1: Float) {
hitboxStart = Point2f(x1, y1)
hitboxEnd = Point2f(x1 + width, y1 + height)
}
fun setPositionX(x: Float) {
setPosition(x, posY)
}
fun setPositionY(y: Float) {
setPosition(posX, y)
}
fun setPositionFromPoint(x1: Float, y1: Float) {
hitboxStart = Point2f(x1 - width / 2, y1 - height)
hitboxEnd = Point2f(hitboxStart.x + width, hitboxStart.y + height)
}
fun setPositionXFromPoint(x: Float) {
setPositionFromPoint(x, pointedY)
}
fun setPositionYFromPoint(y: Float) {
setPositionFromPoint(pointedX, y)
}
fun translatePosX(d: Float) {
setPositionX(posX + d)
}
fun translatePosY(d: Float) {
setPositionY(posY + d)
}
fun setDimension(w: Float, h: Float) {
width = w
height = h
}
/**
* Returns x value of start point
* @return top-left point posX
*/
val posX: Float
get() = hitboxStart.x
/**
* Returns y value of start point
* @return top-left point posY
*/
val posY: Float
get() = hitboxStart.y
val centeredX: Float
get() = (hitboxStart.x + hitboxEnd.x) * 0.5f
val centeredY: Float
get() = (hitboxStart.y + hitboxEnd.y) * 0.5f
}

View File

@@ -0,0 +1,19 @@
package net.torvald.terrarum.gameactors
import java.util.*
/**
* Created by minjaesong on 16-03-14.
*/
interface LandHolder {
/**
* Absolute tile index. index(x, y) = y * map.width + x
* The arraylist will be saved in JSON format with GSON.
*/
var houseDesignation: ArrayList<Int>?
fun addHouseTile(x: Int, y: Int);
fun removeHouseTile(x: Int, y: Int);
fun clearHouseDesignation();
}

View File

@@ -0,0 +1,24 @@
package net.torvald.terrarum.gameactors
/**
* Created by minjaesong on 16-03-14.
*/
interface Luminous {
/**
* Recommended implementation:
*
override var luminosity: Char
get() = if (actorValue.get("luminosity") != null) {
actorValue.get("luminosity") as Char
}
else {
0 as Char
}
set(value) {
actorValue.set("luminosity", value)
}
*/
var luminosity: Int
}

View File

@@ -0,0 +1,102 @@
package net.torvald.terrarum.gameactors
import net.torvald.random.HQRNG
import net.torvald.terrarum.gameactors.ai.ActorAI
import net.torvald.terrarum.gameactors.faction.Faction
import net.torvald.terrarum.gameitem.InventoryItem
import net.torvald.terrarum.Terrarum
import org.newdawn.slick.GameContainer
import java.util.*
/**
* Created by minjaesong on 16-03-14.
*/
open class NPCIntelligentBase : ActorWithBody()
, AIControlled, Pocketed, CanBeAnItem, Factionable, LandHolder {
override var itemData: InventoryItem = object : InventoryItem {
override var itemID = HQRNG().nextLong()
override var mass: Float
get() = actorValue.get("mass") as Float
set(value) {
actorValue.set("mass", value)
}
override var scale: Float
get() = actorValue.get("scale") as Float
set(value) {
actorValue.set("scale", value)
}
override fun effectWhileInPocket(gc: GameContainer, delta_t: Int) {
}
override fun effectWhenPickedUp(gc: GameContainer, delta_t: Int) {
}
override fun primaryUse(gc: GameContainer, delta_t: Int) {
}
override fun secondaryUse(gc: GameContainer, delta_t: Int) {
}
override fun effectWhenThrownAway(gc: GameContainer, delta_t: Int) {
}
}
@Transient private var ai: ActorAI? = null
override var inventory: ActorInventory = ActorInventory()
private val factionSet = HashSet<Faction>()
override var referenceID: Long = HQRNG().nextLong()
override var faction: HashSet<Faction> = HashSet()
override var houseDesignation: ArrayList<Int>? = null
/**
* Absolute tile index. index(x, y) = y * map.width + x
* The arraylist will be saved in JSON format with GSON.
*/
private var houseTiles = ArrayList<Int>()
override fun attachItemData() {
}
override fun getItemWeight(): Float {
return mass
}
override fun addHouseTile(x: Int, y: Int) {
houseTiles.add(Terrarum.game.map.width * y + x)
}
override fun removeHouseTile(x: Int, y: Int) {
houseTiles.remove(Terrarum.game.map.width * y + x)
}
override fun clearHouseDesignation() {
houseTiles.clear()
}
override fun stopUpdateAndDraw() {
isUpdate = false
isVisible = false
}
override fun resumeUpdateAndDraw() {
isUpdate = true
isVisible = true
}
override fun attachAI(ai: ActorAI) {
this.ai = ai
}
}

View File

@@ -0,0 +1,31 @@
package net.torvald.terrarum.gameactors
import net.torvald.spriteanimation.SpriteAnimation
import net.torvald.terrarum.mapdrawer.MapDrawer
/**
* Created by minjaesong on 16-03-25.
*/
object PBCynthia {
fun create(): Player {
val p: Player = Player()
CreatureRawInjector.inject(p.actorValue, "CreatureHuman.json")
p.actorValue["selectedtile"] = 16
p.sprite = SpriteAnimation()
p.sprite!!.setDimension(26, 42)
p.sprite!!.setSpriteImage("res/graphics/sprites/test_player_2.png")
p.sprite!!.setDelay(200)
p.sprite!!.setRowsAndFrames(1, 1)
p.sprite!!.setAsVisible()
p.setHitboxDimension(15, p.actorValue.getAsInt(AVKey.BASEHEIGHT) ?: Player.BASE_HEIGHT, 9, 0)
p.setPosition((4096 * MapDrawer.TILE_SIZE).toFloat(), (300 * 16).toFloat())
return p
}
}

View File

@@ -0,0 +1,73 @@
package net.torvald.terrarum.gameactors
import net.torvald.JsonFetcher
import net.torvald.terrarum.gameactors.faction.Faction
import net.torvald.spriteanimation.SpriteAnimation
import com.google.gson.JsonObject
import net.torvald.terrarum.gameactors.faction.FactionFactory
import net.torvald.terrarum.mapdrawer.MapDrawer
import org.newdawn.slick.SlickException
import java.io.IOException
/**
* Created by minjaesong on 16-03-14.
*/
object PBSigrid {
fun create(): Player {
val p = Player()
p.sprite = SpriteAnimation()
p.sprite!!.setDimension(28, 51)
p.sprite!!.setSpriteImage("res/graphics/sprites/test_player.png")
p.sprite!!.setDelay(200)
p.sprite!!.setRowsAndFrames(1, 1)
p.sprite!!.setAsVisible()
p.spriteGlow = SpriteAnimation()
p.spriteGlow!!.setDimension(28, 51)
p.spriteGlow!!.setSpriteImage("res/graphics/sprites/test_player_glow.png")
p.spriteGlow!!.setDelay(200)
p.spriteGlow!!.setRowsAndFrames(1, 1)
p.spriteGlow!!.setAsVisible()
p.actorValue = ActorValue()
p.actorValue[AVKey.SCALE] = 1.0f
p.actorValue[AVKey.SPEED] = 4.0f
p.actorValue[AVKey.SPEEDMULT] = 1.0f
p.actorValue[AVKey.ACCEL] = Player.WALK_ACCEL_BASE
p.actorValue[AVKey.ACCELMULT] = 1.0f
p.actorValue[AVKey.JUMPPOWER] = 5f
p.actorValue[AVKey.BASEMASS] = 80f
p.actorValue[AVKey.PHYSIQUEMULT] = 1 // Constant 1.0 for player, meant to be used by random mobs
/**
* fixed value, or 'base value', from creature strength of Dwarf Fortress.
* Human race uses 1000. (see CreatureHuman.json)
*/
p.actorValue[AVKey.STRENGTH] = 1414
p.actorValue[AVKey.ENCUMBRANCE] = 1000
p.actorValue[AVKey.BASEHEIGHT] = 46
p.actorValue[AVKey.NAME] = "Sigrid"
p.actorValue[AVKey.INTELLIGENT] = true
p.actorValue[AVKey.LUMINOSITY] = 95487100
p.actorValue[AVKey.BASEDEFENCE] = 141
p.actorValue["selectedtile"] = 16
p.setHitboxDimension(15, p.actorValue.getAsInt(AVKey.BASEHEIGHT)!!, 10, 0)
p.inventory = ActorInventory(0x7FFFFFFF, true)
p.setPosition((4096 * MapDrawer.TILE_SIZE).toFloat(), (300 * 16).toFloat())
p.faction.add(FactionFactory.create("FactionSigrid.json"))
return p
}
}

View File

@@ -0,0 +1,31 @@
package net.torvald.terrarum.gameactors
import net.torvald.terrarum.mapgenerator.RoguelikeRandomiser
import org.newdawn.slick.Color
import org.newdawn.slick.GameContainer
import org.newdawn.slick.Graphics
/**
* Created by minjaesong on 16-03-14.
*/
class PhysTestBall : ActorWithBody {
private var color = Color.orange
constructor(): super() {
setHitboxDimension(16, 16, 0, 0)
isVisible = true
mass = 10f
color = RoguelikeRandomiser.composeColourFrom(RoguelikeRandomiser.POTION_PRIMARY_COLSET)
}
override fun drawBody(gc: GameContainer, g: Graphics) {
g.color = color
g.fillOval(
hitbox!!.posX,
hitbox!!.posY,
hitbox!!.width,
hitbox!!.height)
}
}

View File

@@ -0,0 +1,493 @@
package net.torvald.terrarum.gameactors
import net.torvald.terrarum.gameactors.faction.Faction
import net.torvald.terrarum.gamecontroller.EnumKeyFunc
import net.torvald.terrarum.gamecontroller.KeyMap
import net.torvald.terrarum.mapdrawer.MapDrawer
import net.torvald.terrarum.Terrarum
import net.torvald.spriteanimation.SpriteAnimation
import com.jme3.math.FastMath
import org.lwjgl.input.Controller
import org.lwjgl.input.Controllers
import org.newdawn.slick.GameContainer
import org.newdawn.slick.Input
import org.newdawn.slick.SlickException
import java.util.*
/**
* Created by minjaesong on 16-03-14.
*/
class Player : ActorWithBody, Controllable, Pocketed, Factionable, Luminous, LandHolder {
/**
* empirical value.
*/
// private transient final float JUMP_ACCELERATION_MOD = ???f / 10000f; //quadratic mode
@Transient private val JUMP_ACCELERATION_MOD = 170f / 10000f //linear mode
@Transient private val WALK_FRAMES_TO_MAX_ACCEL = 6
@Transient private val LEFT = 1
@Transient private val RIGHT = 2
@Transient private val KEY_NULL = -1
var vehicleRiding: Controllable? = null
internal var jumpCounter = 0
internal var walkPowerCounter = 0
@Transient private val MAX_JUMP_LENGTH = 17 // use 17; in internal frames
private var readonly_totalX = 0f
private var readonly_totalY = 0f
internal var jumping = false
internal var walkHeading: Int = 0
@Transient private var prevHMoveKey = KEY_NULL
@Transient private var prevVMoveKey = KEY_NULL
internal var noClip = false
@Transient private val AXIS_POSMAX = 1.0f
@Transient private val GAMEPAD_JUMP = 5
@Transient private val TSIZE = MapDrawer.TILE_SIZE
private val factionSet = HashSet<Faction>()
@Transient private val BASE_DENSITY = 1020
/** Must be set by PlayerFactory */
override var inventory: ActorInventory = ActorInventory()
/** Must be set by PlayerFactory */
override var faction: HashSet<Faction> = HashSet()
override var houseDesignation: ArrayList<Int>? = null
override var luminosity: Int
get() = actorValue.getAsInt(AVKey.LUMINOSITY) ?: 0
set(value) {
actorValue[AVKey.LUMINOSITY] = value
}
companion object {
@Transient internal const val ACCEL_MULT_IN_FLIGHT = 0.48f
@Transient internal const val WALK_STOP_ACCEL = 0.32f
@Transient internal const val WALK_ACCEL_BASE = 0.32f
@Transient const val PLAYER_REF_ID: Long = 0x51621D
@Transient const val BASE_HEIGHT = 40
}
/**
* Creates new Player instance with empty elements (sprites, actorvalue, etc.).
* **Use PlayerFactory to build player!**
* @throws SlickException
*/
@Throws(SlickException::class)
constructor() : super() {
isVisible = true
referenceID = PLAYER_REF_ID
super.setDensity(BASE_DENSITY)
}
override fun update(gc: GameContainer, delta_t: Int) {
if (vehicleRiding is Player)
throw RuntimeException("Attempted to 'ride' " + "player object.")
super.update(gc, delta_t)
updateSprite(delta_t)
updateMovementControl()
if (noClip) {
grounded = true
}
}
/**
* @param left (even if the game is joypad controlled, you must give valid value)
* *
* @param absAxisVal (set AXIS_POSMAX if keyboard controlled)
*/
private fun walkHorizontal(left: Boolean, absAxisVal: Float) {
//if ((!super.isWalledLeft() && left) || (!super.isWalledRight() && !left)) {
readonly_totalX = veloX +
actorValue.getAsFloat(AVKey.ACCEL)!! *
actorValue.getAsFloat(AVKey.ACCELMULT)!! *
FastMath.sqrt(scale) *
applyAccelRealism(walkPowerCounter) *
(if (left) -1 else 1).toFloat() *
absAxisVal
veloX = readonly_totalX
if (walkPowerCounter < WALK_FRAMES_TO_MAX_ACCEL) {
walkPowerCounter += 1
}
// Clamp veloX
veloX = absClamp(veloX, actorValue.getAsFloat(AVKey.SPEED)!!
* actorValue.getAsFloat(AVKey.SPEEDMULT)!!
* FastMath.sqrt(scale))
// Heading flag
if (left)
walkHeading = LEFT
else
walkHeading = RIGHT
//}
}
/**
* @param up (even if the game is joypad controlled, you must give valid value)
* *
* @param absAxisVal (set AXIS_POSMAX if keyboard controlled)
*/
private fun walkVertical(up: Boolean, absAxisVal: Float) {
readonly_totalY = veloY +
actorValue.getAsFloat(AVKey.ACCEL)!! *
actorValue.getAsFloat(AVKey.ACCELMULT)!! *
FastMath.sqrt(scale) *
applyAccelRealism(walkPowerCounter) *
(if (up) -1 else 1).toFloat() *
absAxisVal
veloY = readonly_totalY
if (walkPowerCounter < WALK_FRAMES_TO_MAX_ACCEL) {
walkPowerCounter += 1
}
// Clamp veloX
veloY = absClamp(veloY, actorValue.getAsFloat(AVKey.SPEED)!!
* actorValue.getAsFloat(AVKey.SPEEDMULT)!!
* FastMath.sqrt(scale))
}
/**
* For realistic accelerating while walking.
* Naïve 'veloX += 3' is actually like:
* a
* | ------------
* |
* |
* 0+------············ t
* which is unrealistic, so this method tries to introduce some realism by doing:
* a
* | ------------
* | ---
* | -
* | ---
* 0+----··················· t
* @param x
*/
private fun applyAccelRealism(x: Int): Float {
return 0.5f + 0.5f * -FastMath.cos(10 * x / (WALK_FRAMES_TO_MAX_ACCEL * FastMath.PI))
}
private fun walkHStop() {
if (veloX > 0) {
veloX -= actorValue.getAsFloat(AVKey.ACCEL)!! *
actorValue.getAsFloat(AVKey.ACCELMULT)!! *
FastMath.sqrt(scale)
// compensate overshoot
if (veloX < 0) veloX = 0f
} else if (veloX < 0) {
veloX += actorValue.getAsFloat(AVKey.ACCEL)!! *
actorValue.getAsFloat(AVKey.ACCELMULT)!! *
FastMath.sqrt(scale)
// compensate overshoot
if (veloX > 0) veloX = 0f
} else {
veloX = 0f
}
walkPowerCounter = 0
}
private fun walkVStop() {
if (veloY > 0) {
veloY -= WALK_STOP_ACCEL *
actorValue.getAsFloat(AVKey.ACCELMULT)!! *
FastMath.sqrt(scale)
// compensate overshoot
if (veloY < 0)
veloY = 0f
} else if (veloY < 0) {
veloY += WALK_STOP_ACCEL *
actorValue.getAsFloat(AVKey.ACCELMULT)!! *
FastMath.sqrt(scale)
// compensate overshoot
if (veloY > 0) veloY = 0f
} else {
veloY = 0f
}
walkPowerCounter = 0
}
private fun updateMovementControl() {
if (!noClip) {
if (grounded) {
actorValue.set(AVKey.ACCELMULT, 1f)
} else {
actorValue.set(AVKey.ACCELMULT, ACCEL_MULT_IN_FLIGHT)
}
} else {
actorValue.set(AVKey.ACCELMULT, 1f)
}
}
override fun processInput(input: Input) {
var gamepad: Controller? = null
var axisX = 0f
var axisY = 0f
var axisRX = 0f
var axisRY = 0f
if (Terrarum.hasController) {
gamepad = Controllers.getController(0)
axisX = gamepad!!.getAxisValue(0)
axisY = gamepad.getAxisValue(1)
axisRX = gamepad.getAxisValue(2)
axisRY = gamepad.getAxisValue(3)
if (Math.abs(axisX) < Terrarum.CONTROLLER_DEADZONE) axisX = 0f
if (Math.abs(axisY) < Terrarum.CONTROLLER_DEADZONE) axisY = 0f
if (Math.abs(axisRX) < Terrarum.CONTROLLER_DEADZONE) axisRX = 0f
if (Math.abs(axisRY) < Terrarum.CONTROLLER_DEADZONE) axisRY = 0f
}
/**
* L-R stop
*/
if (Terrarum.hasController) {
if (axisX == 0f) {
walkHStop()
}
} else {
// ↑F, ↑S
if (!isFuncDown(input, EnumKeyFunc.MOVE_LEFT) && !isFuncDown(input, EnumKeyFunc.MOVE_RIGHT)) {
walkHStop()
prevHMoveKey = KEY_NULL
}
}
/**
* U-D stop
*/
if (Terrarum.hasController) {
if (axisY == 0f) {
walkVStop()
}
} else {
// ↑E
// ↑D
if (isNoClip()
&& !isFuncDown(input, EnumKeyFunc.MOVE_UP)
&& !isFuncDown(input, EnumKeyFunc.MOVE_DOWN)) {
walkVStop()
prevVMoveKey = KEY_NULL
}
}
/**
* Left/Right movement
*/
if (Terrarum.hasController) {
if (axisX != 0f) {
walkHorizontal(axisX < 0, AXIS_POSMAX)
}
} else {
// ↑F, ↓S
if (isFuncDown(input, EnumKeyFunc.MOVE_RIGHT) && !isFuncDown(input, EnumKeyFunc.MOVE_LEFT)) {
walkHorizontal(false, AXIS_POSMAX)
prevHMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_RIGHT)
} else if (isFuncDown(input, EnumKeyFunc.MOVE_LEFT) && !isFuncDown(input, EnumKeyFunc.MOVE_RIGHT)) {
walkHorizontal(true, AXIS_POSMAX)
prevHMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_LEFT)
} else if (isFuncDown(input, EnumKeyFunc.MOVE_LEFT) && isFuncDown(input, EnumKeyFunc.MOVE_RIGHT)) {
if (prevHMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_LEFT)) {
walkHorizontal(false, AXIS_POSMAX)
prevHMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_RIGHT)
} else if (prevHMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_RIGHT)) {
walkHorizontal(true, AXIS_POSMAX)
prevHMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_LEFT)
}
}// ↓F, ↓S
// ↓F, ↑S
}
/**
* Up/Down movement
*/
if (noClip) {
if (Terrarum.hasController) {
if (axisY != 0f) {
walkVertical(axisY > 0, AXIS_POSMAX)
}
} else {
// ↑E
// ↓D
if (isFuncDown(input, EnumKeyFunc.MOVE_DOWN) && !isFuncDown(input, EnumKeyFunc.MOVE_UP)) {
walkVertical(false, AXIS_POSMAX)
prevVMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_DOWN)
} else if (isFuncDown(input, EnumKeyFunc.MOVE_UP) && !isFuncDown(input, EnumKeyFunc.MOVE_DOWN)) {
walkVertical(true, AXIS_POSMAX)
prevVMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_UP)
} else if (isFuncDown(input, EnumKeyFunc.MOVE_UP) && isFuncDown(input, EnumKeyFunc.MOVE_DOWN)) {
if (prevVMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_UP)) {
walkVertical(false, AXIS_POSMAX)
prevVMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_DOWN)
} else if (prevVMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_DOWN)) {
walkVertical(true, AXIS_POSMAX)
prevVMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_UP)
}
}// ↓E
// ↓D
// ↓E
// ↑D
}
}
/**
* Jump control
*/
if (isFuncDown(input, EnumKeyFunc.JUMP) || Terrarum.hasController && gamepad!!.isButtonPressed(GAMEPAD_JUMP)) {
if (!noClip) {
if (grounded) {
jumping = true
}
jump()
} else {
walkVertical(true, AXIS_POSMAX)
}
} else {
jumping = false
jumpCounter = 0
}
}
override fun keyPressed(key: Int, c: Char) {
}
/**
* See ./work_files/Jump\ power\ by\ pressing\ time.gcx
*/
private fun jump() {
if (jumping) {
val len = MAX_JUMP_LENGTH.toFloat()
val pwr = actorValue.getAsFloat(AVKey.JUMPPOWER)!! * (actorValue.getAsFloat(AVKey.JUMPPOWERMULT) ?: 1f)
// increment jump counter
if (jumpCounter < len) jumpCounter += 1
// linear time mode
val init = (len + 1) / 2f
var timedJumpCharge = init - init / len * jumpCounter
if (timedJumpCharge < 0) timedJumpCharge = 0f
val jumpAcc = pwr * timedJumpCharge * JUMP_ACCELERATION_MOD * FastMath.sqrt(scale)
veloY -= jumpAcc
// try concave mode?
}
// for mob ai:
//super.setVeloY(veloY
// -
// pwr * FastMath.sqrt(scale)
//);
}
private fun jumpFuncLin(pwr: Float, len: Float): Float {
return -(pwr / len) * jumpCounter
}
private fun jumpFuncSqu(pwr: Float, len: Float): Float {
return pwr / (len * len) * (jumpCounter - len * jumpCounter - len) - pwr
}
private fun jumpFuncExp(pwr: Float, len: Float): Float {
val a = FastMath.pow(pwr + 1, 1 / len)
return -FastMath.pow(a, len) + 1
}
private fun isFuncDown(input: Input, fn: EnumKeyFunc): Boolean {
return input.isKeyDown(KeyMap.getKeyCode(fn))
}
private fun absClamp(i: Float, ceil: Float): Float {
if (i > 0)
return if (i > ceil) ceil else i
else if (i < 0)
return if (-i > ceil) -ceil else i
else
return 0f
}
private fun updateSprite(delta_t: Int) {
sprite!!.update(delta_t)
if (spriteGlow != null) {
spriteGlow!!.update(delta_t)
}
if (grounded) {
if (walkHeading == LEFT) {
sprite!!.flip(true, false)
if (spriteGlow != null) {
spriteGlow!!.flip(true, false)
}
} else {
sprite!!.flip(false, false)
if (spriteGlow != null) {
spriteGlow!!.flip(false, false)
}
}
}
}
fun isNoClip(): Boolean {
return noClip
}
fun setNoClip(b: Boolean) {
noClip = b
}
override fun addHouseTile(x: Int, y: Int) {
throw UnsupportedOperationException()
}
override fun removeHouseTile(x: Int, y: Int) {
throw UnsupportedOperationException()
}
override fun clearHouseDesignation() {
throw UnsupportedOperationException()
}
}

View File

@@ -0,0 +1,24 @@
package net.torvald.terrarum.gameactors
import org.newdawn.slick.SlickException
import java.io.IOException
/**
* Created by minjaesong on 16-03-15.
*/
object PlayerBuilder {
private val JSONPATH = "./res/raw/"
private val jsonString = String()
@Throws(IOException::class, SlickException::class)
fun create(): Player {
val p: Player = Player()
CreatureRawInjector.inject(p.actorValue, "CreatureHuman.json")
// attach sprite
// do etc.
return p
}
}

View File

@@ -0,0 +1,10 @@
package net.torvald.terrarum.gameactors
/**
* Created by minjaesong on 16-03-14.
*/
interface Pocketed {
var inventory: ActorInventory
}

View File

@@ -0,0 +1,13 @@
package net.torvald.terrarum.gameactors
import org.newdawn.slick.GameContainer
import org.newdawn.slick.Graphics
/**
* Created by minjaesong on 16-03-14.
*/
interface Visible {
fun drawBody(gc: GameContainer, g: Graphics)
fun updateBodySprite(gc: GameContainer, delta: Int)
}

View File

@@ -0,0 +1,7 @@
package net.torvald.terrarum.gameactors.ai
/**
* Created by minjaesong on 16-03-14.
*/
interface ActorAI {
}

View File

@@ -0,0 +1,62 @@
package net.torvald.terrarum.gameactors.faction
import net.torvald.random.HQRNG
import java.util.HashSet
/**
* Created by minjaesong on 16-02-15.
*/
class Faction(factionName: String) {
lateinit var factionName: String
lateinit var factionAmicable: HashSet<String>
lateinit var factionNeutral: HashSet<String>
lateinit var factionHostile: HashSet<String>
lateinit var factionFearful: HashSet<String>
var factionID: Long = HQRNG().nextLong()
init {
this.factionName = factionName
factionAmicable = HashSet<String>()
factionNeutral = HashSet<String>()
factionHostile = HashSet<String>()
factionFearful = HashSet<String>()
}
fun renewFactionName(factionName: String) {
this.factionName = factionName
}
fun addFactionAmicable(faction: String) {
factionAmicable.add(faction)
}
fun addFactionNeutral(faction: String) {
factionNeutral.add(faction)
}
fun addFactionHostile(faction: String) {
factionHostile.add(faction)
}
fun addFactionFearful(faction: String) {
factionFearful.add(faction)
}
fun removeFactionAmicable(faction: String) {
factionAmicable.remove(faction)
}
fun removeFactionNeutral(faction: String) {
factionNeutral.remove(faction)
}
fun removeFactionHostile(faction: String) {
factionHostile.remove(faction)
}
fun removeFactionFearful(faction: String) {
factionFearful.remove(faction)
}
}

View File

@@ -0,0 +1,31 @@
package net.torvald.terrarum.gameactors.faction
import net.torvald.JsonFetcher
import com.google.gson.JsonObject
import java.io.IOException
/**
* Created by minjaesong on 16-02-15.
*/
object FactionFactory {
const val JSONPATH = "./res/raw/factions/"
/**
* @param filename with extension
*/
@Throws(IOException::class)
fun create(filename: String): Faction {
val jsonObj = JsonFetcher.readJson(JSONPATH + filename)
val factionObj = Faction(jsonObj.get("factionname").asString)
jsonObj.get("factionamicable").asJsonArray.forEach { s -> factionObj.addFactionAmicable(s.asString) }
jsonObj.get("factionneutral").asJsonArray.forEach { s -> factionObj.addFactionNeutral(s.asString) }
jsonObj.get("factionhostile").asJsonArray.forEach { s -> factionObj.addFactionHostile(s.asString) }
jsonObj.get("factionfearful").asJsonArray.forEach { s -> factionObj.addFactionFearful(s.asString) }
return factionObj
}
}

View File

@@ -0,0 +1,8 @@
package net.torvald.terrarum.gameactors.scheduler
/**
* Ultima-like NPC scheduler
* Created by minjaesong on 16-03-26.
*/
interface NPCSchedule {
}

View File

@@ -0,0 +1,9 @@
package net.torvald.terrarum.gamecontroller
/**
* Created by minjaesong on 15-12-31.
*/
enum class EnumKeyFunc {
UI_CONSOLE, UI_BASIC_INFO,
MOVE_LEFT, MOVE_RIGHT, MOVE_UP, MOVE_DOWN, JUMP
}

View File

@@ -0,0 +1,122 @@
package net.torvald.terrarum.gamecontroller
import net.torvald.terrarum.gameactors.Controllable
import net.torvald.terrarum.gameactors.Player
import net.torvald.terrarum.mapdrawer.MapCamera
import net.torvald.terrarum.mapdrawer.MapDrawer
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.tileproperties.TileNameCode
import net.torvald.terrarum.tileproperties.TilePropCodex
import net.torvald.terrarum.ui.UIHandler
import org.newdawn.slick.Input
/**
* Created by minjaesong on 15-12-31.
*/
object GameController {
fun processInput(input: Input) {
val mouseTileX = ((MapCamera.cameraX + input.mouseX / Terrarum.game.screenZoom) / MapDrawer.TILE_SIZE).toInt()
val mouseTileY = ((MapCamera.cameraY + input.mouseY / Terrarum.game.screenZoom) / MapDrawer.TILE_SIZE).toInt()
KeyToggler.update(input)
if (!Terrarum.game.consoleHandler.isTakingControl) {
if (Terrarum.game.player.vehicleRiding != null) {
Terrarum.game.player.vehicleRiding!!.processInput(input)
}
Terrarum.game.player.processInput(input)
for (ui in Terrarum.game.uiContainer) {
ui.processInput(input)
}
}
else {
Terrarum.game.consoleHandler.processInput(input)
}
if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) {
// test tile remove
try {
Terrarum.game.map.setTileTerrain(mouseTileX, mouseTileY, TileNameCode.AIR)
// terrarum.game.map.setTileWall(mouseTileX, mouseTileY, TileNameCode.AIR);
}
catch (e: ArrayIndexOutOfBoundsException) {
}
}
else if (input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) {
// test tile place
try {
Terrarum.game.map.setTileTerrain(mouseTileX, mouseTileY, Terrarum.game.player.actorValue.getAsInt("selectedtile")!!)
}
catch (e: ArrayIndexOutOfBoundsException) {
}
}
}
fun keyPressed(key: Int, c: Char) {
if (keyPressedByCode(key, EnumKeyFunc.UI_CONSOLE)) {
Terrarum.game.consoleHandler.toggleOpening()
}
else if (keyPressedByCode(key, EnumKeyFunc.UI_BASIC_INFO)) {
Terrarum.game.debugWindow.toggleOpening()
}
if (!Terrarum.game.consoleHandler.isTakingControl) {
if (Terrarum.game.player.vehicleRiding != null) {
Terrarum.game.player.vehicleRiding!!.keyPressed(key, c)
}
Terrarum.game.player.keyPressed(key, c)
}
else {
Terrarum.game.consoleHandler.keyPressed(key, c)
}
//System.out.println(String.valueOf(key) + ", " + String.valueOf(c));
}
fun keyReleased(key: Int, c: Char) {
}
fun mouseMoved(oldx: Int, oldy: Int, newx: Int, newy: Int) {
}
fun mouseDragged(oldx: Int, oldy: Int, newx: Int, newy: Int) {
}
fun mousePressed(button: Int, x: Int, y: Int) {
}
fun mouseReleased(button: Int, x: Int, y: Int) {
}
fun mouseWheelMoved(change: Int) {
}
fun controllerButtonPressed(controller: Int, button: Int) {
}
fun controllerButtonReleased(controller: Int, button: Int) {
}
private fun keyPressedByCode(key: Int, fn: EnumKeyFunc): Boolean {
return KeyMap.getKeyCode(fn) == key
}
}

View File

@@ -0,0 +1,86 @@
package net.torvald.terrarum.gamecontroller
/**
* Created by minjaesong on 16-01-15.
*/
object Key {
val RET = 28
val BKSP = 14
val GRAVE = 41
val TAB = 15
val ESCAPE = 1
val SPACE = 57
val L_SHIFT = 42
val R_SHIFT = 54
val UP = 200
val DOWN = 208
val LEFT = 203
val RIGHT = 205
val F1 = 59
val F2 = 60
val F3 = 61
val F4 = 62
val F5 = 63
val F6 = 64
val F7 = 65
val F8 = 66
val F9 = 67
val F10 = 68
val F11 = 87
val F12 = 88
val NUM_1 = 2
val NUM_2 = 3
val NUM_3 = 4
val NUM_4 = 5
val NUM_5 = 6
val NUM_6 = 7
val NUM_7 = 8
val NUM_8 = 9
val NUM_9 = 10
val NUM_0 = 11
val Q = 16
val W = 17
val E = 18
val R = 19
val T = 20
val Y = 21
val U = 22
val I = 23
val O = 24
val P = 25
val A = 30
val S = 31
val D = 32
val F = 33
val G = 34
val H = 35
val J = 36
val K = 37
val L = 38
val SEMICOLON = 39
val Z = 44
val X = 45
val C = 46
val V = 47
val B = 48
val N = 49
val M = 50
val PGUP = 201
val PGDN = 209
val HOME = 199
val END = 207
}

View File

@@ -0,0 +1,31 @@
package net.torvald.terrarum.gamecontroller
import java.util.Hashtable
/**
* Created by minjaesong on 15-12-31.
*/
object KeyMap {
var map_code = Hashtable<EnumKeyFunc, Int>()
fun build() {
map_code.put(EnumKeyFunc.MOVE_UP, Key.E)
map_code.put(EnumKeyFunc.MOVE_LEFT, Key.S)
map_code.put(EnumKeyFunc.MOVE_DOWN, Key.D)
map_code.put(EnumKeyFunc.MOVE_RIGHT, Key.F)
map_code.put(EnumKeyFunc.JUMP, Key.SPACE)
map_code.put(EnumKeyFunc.UI_CONSOLE, Key.GRAVE)
map_code.put(EnumKeyFunc.UI_BASIC_INFO, Key.F3)
}
fun getKeyCode(fn: EnumKeyFunc): Int {
return map_code[fn]!!
}
operator fun set(func: EnumKeyFunc, key: Int) {
map_code.put(func, key)
}
}

View File

@@ -0,0 +1,48 @@
package net.torvald.terrarum.gamecontroller
import org.newdawn.slick.GameContainer
import org.newdawn.slick.Input
import java.util.*
object KeyToggler {
private val currentState = BitSet(256)
private val isPressed = BitSet(256)
private val isToggled = BitSet(256)
fun isOn(key: Int): Boolean {
return currentState[key]
}
fun update(input: Input) {
for (i in 0..255) {
if (input.isKeyDown(i)) {
isPressed[i] = true
}
else {
isPressed[i] = false
}
}
for (i in 0..255) {
if (isPressed[i] && !currentState[i] && !isToggled[i]) {
currentState[i] = true
isToggled[i] = true
}
else if (isPressed[i] && currentState[i] && !isToggled[i]) {
currentState[i] = false
isToggled[i] = true
}
if (!isPressed[i] && isToggled[i]) {
isToggled[i] = false
}
}
}
fun forceSet(key: Int, b: Boolean) {
currentState[key] = b
isToggled[key] = true
}
}

View File

@@ -0,0 +1,66 @@
package net.torvald.terrarum.gameitem
import org.newdawn.slick.GameContainer
/**
* Created by minjaesong on 16-01-16.
*/
interface InventoryItem {
/**
* Internal ID of an Item, Long
* 0-4096: Tiles
* 4097-32767: Various items
* >=32768: Actor RefID
*/
var itemID: Long
/**
* Weight of the item, Float
*/
var mass: Float
/**
* Scale of the item. Real mass: mass * (scale^3)
*/
var scale: Float
/**
* Effects applied while in pocket
* @param gc
* *
* @param delta_t
*/
fun effectWhileInPocket(gc: GameContainer, delta_t: Int)
/**
* Effects applied immediately only once if picked up
* @param gc
* *
* @param delta_t
*/
fun effectWhenPickedUp(gc: GameContainer, delta_t: Int)
/**
* Effects applied while primary button (usually left mouse button) is down
* @param gc
* *
* @param delta_t
*/
fun primaryUse(gc: GameContainer, delta_t: Int)
/**
* Effects applied while secondary button (usually right mouse button) is down
* @param gc
* *
* @param delta_t
*/
fun secondaryUse(gc: GameContainer, delta_t: Int)
/**
* Effects applied immediately only once if thrown from pocket
* @param gc
* *
* @param delta_t
*/
fun effectWhenThrownAway(gc: GameContainer, delta_t: Int)
}

View File

@@ -0,0 +1,39 @@
package net.torvald.terrarum.gameitem
import net.torvald.terrarum.tileproperties.TilePropCodex
import org.newdawn.slick.GameContainer
/**
* Created by minjaesong on 16-03-15.
*/
class TileAsItem(tileNum: Int) : InventoryItem {
override var itemID: Long = -1
override var mass: Float = 0f
override var scale: Float = 1f
init {
itemID = tileNum.toLong()
mass = TilePropCodex.getProp(tileNum).density / 1000f
}
override fun effectWhileInPocket(gc: GameContainer, delta_t: Int) {
throw UnsupportedOperationException()
}
override fun effectWhenPickedUp(gc: GameContainer, delta_t: Int) {
throw UnsupportedOperationException()
}
override fun primaryUse(gc: GameContainer, delta_t: Int) {
throw UnsupportedOperationException()
}
override fun secondaryUse(gc: GameContainer, delta_t: Int) {
throw UnsupportedOperationException()
}
override fun effectWhenThrownAway(gc: GameContainer, delta_t: Int) {
throw UnsupportedOperationException()
}
}

View File

@@ -0,0 +1,223 @@
package net.torvald.terrarum.gamemap
import org.newdawn.slick.SlickException
class GameMap
/**
* @param width
* *
* @param height
* *
* @throws SlickException
*/
@Throws(SlickException::class)
constructor(//properties
val width: Int, val height: Int) {
//layers
val layerWall: MapLayer
/**
* Get MapLayer object of terrain
* @return MapLayer terrain layer
*/
val layerTerrain: MapLayer
val layerWire: MapLayer
val wallDamage: PairedMapLayer
val terrainDamage: PairedMapLayer
val spawnX: Int
val spawnY: Int
//public World physWorld = new World( new Vec2(0, -TerrarumMain.game.gravitationalAccel) );
//physics
/** \[m / s^2\] */
var gravitation: Float = 9.8.toFloat()
/** RGB in Integer */
var globalLight: Int = 0
val worldTime: WorldTime
init {
this.spawnX = width / 2
this.spawnY = 200
layerTerrain = MapLayer(width, height)
layerWall = MapLayer(width, height)
layerWire = MapLayer(width, height)
terrainDamage = PairedMapLayer(width, height)
wallDamage = PairedMapLayer(width, height)
globalLight = 0.toChar().toInt()
worldTime = WorldTime()
}
/**
* Get 2d array data of terrain
* @return byte[][] terrain layer
*/
val terrainArray: Array<ByteArray>
get() = layerTerrain.data
/**
* Get 2d array data of wall
* @return byte[][] wall layer
*/
val wallArray: Array<ByteArray>
get() = layerWall.data
/**
* Get 2d array data of wire
* @return byte[][] wire layer
*/
val wireArray: Array<ByteArray>
get() = layerWire.data
/**
* Get paired array data of damage codes.
* Format: 0baaaabbbb, aaaa for x = 0, 2, 4, ..., bbbb for x = 1, 3, 5, ...
* @return byte[][] damage code pair
*/
val damageDataArray: Array<ByteArray>
get() = terrainDamage.dataPair
fun getTileFromWall(x: Int, y: Int): Int? {
val wall: Int? = layerWall.getTile(x, y)
val wallDamage: Int? = getWallDamage(x, y)
return if (wall == null || wallDamage == null)
null
else
wall * PairedMapLayer.RANGE + wallDamage
}
fun getTileFromTerrain(x: Int, y: Int): Int? {
val terrain: Int? = layerTerrain.getTile(x, y)
val terrainDamage: Int? = getTerrainDamage(x, y)
return if (terrain == null || terrainDamage == null)
null
else
terrain * PairedMapLayer.RANGE + terrainDamage
}
fun getTileFromWire(x: Int, y: Int): Int? {
return layerWire.getTile(x, y)
}
fun getWallDamage(x: Int, y: Int): Int? {
return wallDamage.getData(x, y)
}
fun getTerrainDamage(x: Int, y: Int): Int? {
return terrainDamage.getData(x, y)
}
/**
* Set the tile of wall as specified, with damage value of zero.
* @param x
* *
* @param y
* *
* @param combinedTilenum (tilenum * 16) + damage
*/
fun setTileWall(x: Int, y: Int, combinedTilenum: Int) {
setTileWall(x, y, (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
}
/**
* Set the tile of wall as specified, with damage value of zero.
* @param x
* *
* @param y
* *
* @param combinedTilenum (tilenum * 16) + damage
*/
fun setTileTerrain(x: Int, y: Int, combinedTilenum: Int) {
setTileTerrain(x, y, (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
}
fun setTileWall(x: Int, y: Int, tile: Byte, damage: Int) {
layerWall.setTile(x, y, tile)
wallDamage.setData(x, y, damage)
}
fun setTileTerrain(x: Int, y: Int, tile: Byte, damage: Int) {
layerTerrain.setTile(x, y, tile)
terrainDamage.setData(x, y, damage)
}
fun setTileWire(x: Int, y: Int, tile: Byte) {
layerWire.data[y][x] = tile
}
fun getTileFrom(mode: Int, x: Int, y: Int): Int? {
if (mode == TERRAIN) {
return getTileFromTerrain(x, y)
}
else if (mode == WALL) {
return getTileFromWall(x, y)
}
else if (mode == WIRE) {
return getTileFromWire(x, y)
}
else
throw IllegalArgumentException("illegal mode input: " + mode.toString())
}
fun updateWorldTime(delta: Int) {
worldTime.update(delta)
}
fun terrainIterator(): Iterator<Int> {
return object : Iterator<Int> {
private var iteratorCount = 0
override fun hasNext(): Boolean {
return iteratorCount < width * height
}
override fun next(): Int {
val y = iteratorCount / width
val x = iteratorCount % width
// advance counter
iteratorCount += 1
return getTileFromTerrain(x, y)!!
}
}
}
fun wallIterator(): Iterator<Int> {
return object : Iterator<Int> {
private var iteratorCount = 0
override fun hasNext(): Boolean =
iteratorCount < width * height
override fun next(): Int {
val y = iteratorCount / width
val x = iteratorCount % width
// advance counter
iteratorCount += 1
return getTileFromWall(x, y)!!
}
}
}
companion object {
@Transient val WALL = 0
@Transient val TERRAIN = 1
@Transient val WIRE = 2
@Transient val TILES_SUPPORTED = MapLayer.RANGE * PairedMapLayer.RANGE
@Transient val BITS: Byte = 1 // 1 for Byte, 2 for Char, 4 for Int, 8 for Long
@Transient val LAYERS: Byte = 4 // terrain, wall (terrainDamage + wallDamage), wire
}
}

View File

@@ -0,0 +1,56 @@
package net.torvald.terrarum.gamemap
/**
* Created by minjaesong on 16-01-17.
*/
class MapLayer(var width: Int, var height: Int) : Iterable<Byte> {
internal var data: Array<ByteArray>
init {
data = Array(height) { ByteArray(width) }
}
/**
* Returns an iterator over elements of type `T`.
* @return an Iterator.
*/
override fun iterator(): Iterator<Byte> {
return object : Iterator<Byte> {
private var iteratorCount = 0
override fun hasNext(): Boolean {
return iteratorCount < width * height
}
override fun next(): Byte {
val y = iteratorCount / width
val x = iteratorCount % width
// advance counter
iteratorCount += 1
return data[y][x]
}
}
}
internal fun getTile(x: Int, y: Int): Int? {
return if (x !in 0..width - 1 || y !in 0..height - 1)
null
else
uint8ToInt32(data[y][x])
}
internal fun setTile(x: Int, y: Int, tile: Byte) {
data[y][x] = tile
}
private fun uint8ToInt32(x: Byte): Int = java.lang.Byte.toUnsignedInt(x)
companion object {
@Transient val RANGE = 256
}
}

View File

@@ -0,0 +1,35 @@
package net.torvald.terrarum.gamemap
import net.torvald.point.Point2f
import java.io.Serializable
class MapPoint {
var startPoint: Point2f? = null
private set
var endPoint: Point2f? = null
private set
constructor() {
}
constructor(p1: Point2f, p2: Point2f) {
setPoint(p1, p2)
}
constructor(x1: Int, y1: Int, x2: Int, y2: Int) {
setPoint(x1, y1, x2, y2)
}
fun setPoint(p1: Point2f, p2: Point2f) {
startPoint = p1
endPoint = p2
}
fun setPoint(x1: Int, y1: Int, x2: Int, y2: Int) {
startPoint = Point2f(x1.toFloat(), y1.toFloat())
endPoint = Point2f(x2.toFloat(), y2.toFloat())
}
}

View File

@@ -0,0 +1,86 @@
package net.torvald.terrarum.gamemap
import java.io.Serializable
import java.util.Spliterator
import java.util.function.Consumer
/**
* Created by minjaesong on 16-02-15.
*/
class PairedMapLayer(width: Int, var height: Int) : Iterable<Byte> {
/**
* 0b_xxxx_yyyy, x for lower index, y for higher index
* e.g.
* 0110 1101 is interpreted as
* 6 for tile 0, 13 for tile 1.
*/
internal var dataPair: Array<ByteArray>
var width: Int = 0
init {
this.width = width / 2
dataPair = Array(height) { ByteArray(width / 2) }
}
/**
* Returns an iterator over elements of type `T`.
* Note: this iterator will return combined damage, that is 0bxxxx_yyyy as whole.
* @return an Iterator.
*/
override fun iterator(): Iterator<Byte> {
return object : Iterator<Byte> {
private var iteratorCount = 0
override fun hasNext(): Boolean {
return iteratorCount < width * height
}
override fun next(): Byte {
val y = iteratorCount / width
val x = iteratorCount % width
// advance counter
iteratorCount += 1
return dataPair[y][x]
}
}
}
internal fun getData(x: Int, y: Int): Int? {
return if (x !in 0..width * 2 - 1 || y !in 0..height - 1)
null
else {
if (x and 0x1 == 0)
// higher four bits for i = 0, 2, 4, ...
(java.lang.Byte.toUnsignedInt(dataPair[y][x / 2]) and 0xF0) ushr 4
else
// lower four bits for i = 1, 3, 5, ...
java.lang.Byte.toUnsignedInt(dataPair[y][x / 2]) and 0x0F
}
}
internal fun setData(x: Int, y: Int, data: Int) {
if (data < 0 || data >= 16) throw IllegalArgumentException("[PairedMapLayer] $data: invalid data value.")
if (x and 0x1 == 0)
// higher four bits for i = 0, 2, 4, ...
dataPair[y][x / 2] =
(java.lang.Byte.toUnsignedInt(dataPair[y][x / 2]) and 0x0F
or (data and 0xF shl 4)).toByte()
else
// lower four bits for i = 1, 3, 5, ...
dataPair[y][x / 2] = (java.lang.Byte.toUnsignedInt(dataPair[y][x / 2]) and 0xF0
or (data and 0xF)).toByte()
}
companion object {
@Transient val RANGE = 16
}
}

View File

@@ -0,0 +1,153 @@
package net.torvald.terrarum.gamemap
/**
* Created by minjaesong on 16-01-24.
*/
class WorldTime {
internal var seconds: Int
internal var minutes: Int
internal var hours: Int
internal var yearlyDays: Int //NOT a calendar day
internal var days: Int
internal var months: Int
internal var years: Int
internal var dayOfWeek: Int //0: Mondag-The first day of weekday
internal var timeDelta = 1
@Transient private var realMillisec: Int
val DAY_NAMES = arrayOf(//daynames are taken from Nynorsk (å -> o)
"Mondag", "Tysdag", "Midtedag" //From Islenska Miðvikudagur
, "Torsdag", "Fredag", "Laurdag", "Sundag", "Verdag" //From Norsk word 'verd'
)
val DAY_NAMES_SHORT = arrayOf("Mon", "Tys", "Mid", "Tor", "Fre", "Lau", "Sun", "Ver")
@Transient val REAL_SEC_IN_MILLI = 1000
init {
seconds = 0
minutes = 30
hours = 8
yearlyDays = 1
days = 12
months = 3
years = 125
dayOfWeek = 0
realMillisec = 0
}
fun update(delta: Int) {
//time
realMillisec += delta * timeDelta
seconds = Math.round(GAME_MIN_TO_REAL_SEC.toFloat() / REAL_SEC_IN_MILLI.toFloat() * realMillisec.toFloat())
if (realMillisec >= REAL_SEC_IN_MILLI)
realMillisec -= REAL_SEC_IN_MILLI
kickVariables()
}
/**
* How much time has passed today, in seconds.
* 0 == 6 AM
* @return
*/
fun elapsedSeconds(): Int {
return (HOUR_SEC * hours + MINUTE_SEC * minutes + seconds) % DAY_LENGTH
}
val isLeapYear: Boolean
get() = years % 4 == 0 && years % 100 != 0 || years % 400 == 0
fun setTime(t: Int) {
days += t / DAY_LENGTH
hours = t / HOUR_SEC
minutes = (t - HOUR_SEC * hours) / MINUTE_SEC
seconds = t - minutes * MINUTE_SEC
}
fun addTime(t: Int) {
setTime(elapsedSeconds() + t)
}
fun setTimeDelta(d: Int) {
timeDelta = if (d < 0) 0 else d
}
val dayName: String
get() = DAY_NAMES[dayOfWeek]
private fun kickVariables() {
if (seconds >= MINUTE_SEC) {
seconds = 0
minutes += 1
}
if (minutes >= HOUR_MIN) {
minutes = 0
hours += 1
}
if (hours >= DAY_LENGTH / HOUR_SEC) {
hours = 0
days += 1
yearlyDays += 1
dayOfWeek += 1
}
//calendar (the world calendar)
if (dayOfWeek == 7) {
dayOfWeek = 0
}
if ((months == 12 || months == 7 && isLeapYear) && days == 31) {
dayOfWeek = 7
}
if ((months == 12 || months == 7 && isLeapYear) && days == 32) {
days = 1
months = 1
years++
}
else if ((months == 1 || months == 4 || months == 7 || months == 10) && days > 31) {
days = 1
months++
}
else if (days > 30) {
days = 1
months++
}
if (months > 12) {
months = 1
years++
}
}
fun getFormattedTime(): String {
fun formatMin(min: Int): String {
return if (min < 10) "0${min.toString()}" else min.toString()
}
return "${hours}h${formatMin(minutes)}"
}
fun getDayNameFull(): String = DAY_NAMES[dayOfWeek]
fun getDayNameShort(): String = DAY_NAMES_SHORT[dayOfWeek]
companion object {
/**
* 22h
*/
val DAY_LENGTH = 79200 //must be the multiple of 3600
val HOUR_SEC: Int = 3600
val MINUTE_SEC: Int = 60
val HOUR_MIN: Int = 60
val GAME_MIN_TO_REAL_SEC: Float = 60f
}
}

View File

@@ -0,0 +1,9 @@
package net.torvald.terrarum.itemproperties
/**
* Created by minjaesong on 16-03-18.
*/
internal data class ItemProp (
var baseMass: Float,
var material: Material
)

View File

@@ -0,0 +1,44 @@
package net.torvald.terrarum.itemproperties
import net.torvald.terrarum.gameactors.CanBeAnItem
import net.torvald.terrarum.gameitem.InventoryItem
import net.torvald.terrarum.Terrarum
import org.newdawn.slick.GameContainer
import java.util.*
/**
* Created by minjaesong on 16-03-15.
*/
object ItemPropCodex {
val CSV_PATH = "./src/com/torvald/terrarum/itemproperties/itemprop.csv"
/**
* <ItemID or RefID for Actor, TheItem>
* Will return corresponding Actor if ID >= 32768
*/
private lateinit var itemCodex: Array<InventoryItem>
@JvmStatic val ITEM_UNIQUE_MAX = 32768
@JvmStatic
fun buildItemProp() {
itemCodex = arrayOf<InventoryItem>()
// read prop in csv
}
fun getItem(code: Long): InventoryItem {
if (code < ITEM_UNIQUE_MAX)
return itemCodex[(code and 0xFFFFFFFF).toInt()]
else {
for (actor in Terrarum.game.actorContainer) {
if (actor is CanBeAnItem && actor.referenceID == code)
return actor.itemData
}
throw NullPointerException()
}
}
}

View File

@@ -0,0 +1,10 @@
package net.torvald.terrarum.itemproperties
/**
* Created by minjaesong on 16-03-18.
*/
internal data class Material (
var maxEdge: Int,
var hardness: Int,
var density: Int
)

View File

@@ -0,0 +1,7 @@
package net.torvald.terrarum.itemproperties
/**
* Created by minjaesong on 16-03-19.
*/
object MaterialFactory {
}

View File

@@ -0,0 +1,9 @@
package net.torvald.terrarum.itemproperties
/**
* Created by minjaesong on 16-03-18.
*/
object MaterialPropCodex {
val CSV_PATH = "./src/com/torvald/terrarum/itemproperties/materialprop.csv"
}

View File

@@ -0,0 +1,192 @@
package net.torvald.terrarum.langpack
import net.torvald.CSVFetcher
import net.torvald.imagefont.GameFontWhite
import net.torvald.terrarum.Terrarum
import org.apache.commons.csv.CSVRecord
import org.newdawn.slick.SlickException
import java.io.*
import java.util.*
/**
* Created by minjaesong on 16-01-22.
*/
object Lang {
private val CSV_COLUMN_FIRST = "STRING_ID"
/**
* Get record by its STRING_ID
*/
private var lang: HashMap<String, CSVRecord>
private val FALLBACK_LANG_CODE = "enUS"
private val HANGUL_SYL_START = 0xAC00
private val PATH_TO_CSV = "./res/locales/"
private val CSV_MAIN = "polyglot.csv"
private val NAMESET_PREFIX = "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 FRENCH_WORD_NORMAL_PLURAL = arrayOf("bal", "banal", "fatal", "final")
init {
lang = HashMap<String, CSVRecord>()
// 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) }
}
// 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)
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()
}
return value
}
fun pluraliseLang(key: String, count: Int): String {
return if (count > 1) get(key + "_PLURAL") else get(key)
}
fun pluralise(word: String, count: Int): String {
if (count < 2) return word
when (Terrarum.gameLocale) {
"fr" -> {
if (Arrays.binarySearch(FRENCH_WORD_NORMAL_PLURAL, word) >= 0) {
return word + "s"
}
if (word.endsWith("al") || word.endsWith("au") || word.endsWith("eu") || word.endsWith("eau")) {
return word.substring(0, word.length - 2) + "ux"
}
else if (word.endsWith("ail")) {
return word.substring(0, word.length - 3) + "ux"
}
else {
return word + "s"
}
}
"en" -> {
if (Arrays.binarySearch(ENGLISH_WORD_NORMAL_PLURAL, word) >= 0) {
return word + "s"
}
else if (word.endsWith("f")) {
// f -> ves
return word.substring(0, word.length - 2) + "ves"
}
else if (word.endsWith("o") || word.endsWith("z")) {
// o -> oes
return word + "es"
}
else {
return word + "s"
}
}
else -> {
if (Arrays.binarySearch(ENGLISH_WORD_NORMAL_PLURAL, word) >= 0) {
return word + "s"
}
else if (word.endsWith("f")) {
return word.substring(0, word.length - 2) + "ves"
}
else if (word.endsWith("o") || word.endsWith("z")) {
return word + "es"
}
else {
return word + "s"
}
}
}
}
fun postEunNeun(word: String): String {
val lastChar = getLastChar(word)
if (isHangul(lastChar)) {
val index = lastChar.toInt() - HANGUL_SYL_START
return if (index % 28 == 0) word + "" else word + ""
}
else if (lastChar >= 'A' && lastChar <= 'Z' || lastChar >= 'a' && lastChar <= 'z') {
val index = (lastChar.toInt() - 0x41) % 0x20
return if (HANGUL_POST_INDEX_ALPH[index] == 0) word + "" else word + ""
}
else {
return "은(는)"
}
}
fun postIiGa(word: String): String {
val lastChar = getLastChar(word)
if (isHangul(lastChar)) {
val index = lastChar.toInt() - HANGUL_SYL_START
return if (index % 28 == 0) word + "" else word + ""
}
else if (lastChar >= 'A' && lastChar <= 'Z' || lastChar >= 'a' && lastChar <= 'z') {
val index = (lastChar.toInt() - 0x41) % 0x20
return if (HANGUL_POST_INDEX_ALPH[index] == 0) word + "" else word + ""
}
else {
return "이(가)"
}
}
private fun isHangul(c: Char): Boolean {
return c.toInt() >= 0xAC00 && c.toInt() <= 0xD7A3
}
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

@@ -0,0 +1,562 @@
package net.torvald.terrarum.mapdrawer
import net.torvald.terrarum.gameactors.ActorWithBody
import net.torvald.terrarum.gameactors.Luminous
import net.torvald.terrarum.gamemap.WorldTime
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.tileproperties.TilePropCodex
import com.jme3.math.FastMath
import net.torvald.terrarum.tileproperties.TileNameCode
import org.newdawn.slick.Color
import org.newdawn.slick.Graphics
import java.util.*
/**
* Created by minjaesong on 16-01-25.
*/
object LightmapRenderer {
/**
* 8-Bit RGB values
*/
@Volatile private var lightmap: Array<IntArray> = Array(Terrarum.game.map.height) { IntArray(Terrarum.game.map.width) }
private var lightMapInitialised = false
private val AIR = 0
private val SUNSTONE = 41 // TODO add sunstone: emits same light as Map.GL. Goes dark at night
private val OFFSET_R = 2
private val OFFSET_G = 1
private val OFFSET_B = 0
private const val TSIZE = MapDrawer.TILE_SIZE
// color model related constants
const val MUL = 1024 // modify this to 1024 to implement 30-bit RGB
const val MUL_2 = MUL * MUL
const val CHANNEL_MAX = MUL - 1
const val CHANNEL_MAX_FLOAT = CHANNEL_MAX.toFloat()
const val COLOUR_RANGE_SIZE = MUL * MUL_2
fun getLight(x: Int, y: Int): Int? =
if (x !in 0..Terrarum.game.map.width - 1 || y !in 0..Terrarum.game.map.height - 1)
// if out of range then
null
else
lightmap[y][x]
fun setLight(x: Int, y: Int, colour: Int) {
lightmap[y][x] = colour
}
fun renderLightMap() {
val for_x_start = MapCamera.cameraX / TSIZE - 1 // fix for premature lightmap rendering
val for_y_start = MapCamera.cameraY / TSIZE - 1 // on topmost/leftmost side
val for_x_end = for_x_start + MapCamera.getRenderWidth() / TSIZE + 3
val for_y_end = for_y_start + MapCamera.getRenderHeight() / TSIZE + 2 // same fix as above
val overscan_open: Int = (256f / (TilePropCodex.getProp(TileNameCode.AIR).opacity and 0xFF).toFloat()).ceil()
val overscan_opaque: Int = (256f / (TilePropCodex.getProp(TileNameCode.STONE).opacity and 0xFF).toFloat()).ceil()
/**
* * true: overscanning is limited to 8 tiles in width (overscan_opaque)
* * false: overscanning will fully applied to 32 tiles in width (overscan_open)
*/
val noop_mask = BitSet(2 * (for_x_end - for_x_start + 1) +
2 * (for_y_end - for_y_start - 1))
/**
* Updating order:
* +--------+ +--+-----+ +-----+--+ +--------+ -
* |↘ | | | 3| |3 | | | ↙| ↕︎ overscan_open / overscan_opaque
* | +-----+ | | 2 | | 2 | | +-----+ | - depending on the noop_mask
* | |1 | → | |1 | → | 1| | → | 1| |
* | | 2 | | +-----+ +-----+ | | 2 | |
* | | 3| |↗ | | ↖| |3 | |
* +--+-----+ +--------+ +--------+ +-----+--+
* round: 1 2 3 4
* for all staticLightMap[y][x]
*/
purgePartOfLightmap(for_x_start - overscan_open, for_y_start - overscan_open, for_x_end + overscan_open, for_y_end + overscan_open)
try {
// Round 1
for (y in for_y_start - overscan_open..for_y_end) {
for (x in for_x_start - overscan_open..for_x_end) {
setLight(x, y, calculate(x, y))
}
}
// Round 2
for (y in for_y_end + overscan_open downTo for_y_start) {
for (x in for_x_start - overscan_open..for_x_end) {
setLight(x, y, calculate(x, y))
}
}
// Round 3
for (y in for_y_end + overscan_open downTo for_y_start) {
for (x in for_x_end + overscan_open downTo for_x_start) {
setLight(x, y, calculate(x, y))
}
}
// Round 4
for (y in for_y_start - overscan_open..for_y_end) {
for (x in for_x_end + overscan_open downTo for_x_start) {
setLight(x, y, calculate(x, y))
}
}
}
catch (e: ArrayIndexOutOfBoundsException) {
}
}
private fun calculate(x: Int, y: Int): Int = calculate(x, y, false)
private fun calculate(x: Int, y: Int, doNotCalculateAmbient: Boolean): Int {
var lightLevelThis: Int = 0
val thisTerrain = Terrarum.game.map.getTileFromTerrain(x, y)
val thisWall = Terrarum.game.map.getTileFromWall(x, y)
val thisTileLuminosity = TilePropCodex.getProp(thisTerrain).luminosity
val thisTileOpacity = TilePropCodex.getProp(thisTerrain).opacity
val sunLight = Terrarum.game.map.globalLight
// MIX TILE
// open air
if (thisTerrain == AIR && thisWall == AIR) {
lightLevelThis = sunLight
}
// luminous tile on top of air
else if (thisWall == AIR && thisTileLuminosity.toInt() > 0) {
val darkenSunlight = darkenColoured(sunLight, thisTileOpacity)
lightLevelThis = maximiseRGB(darkenSunlight, thisTileLuminosity) // maximise to not exceed 1.0 with normal (<= 1.0) light
}
// opaque wall and luminous tile
else if (thisWall != AIR && thisTileLuminosity.toInt() > 0) {
lightLevelThis = thisTileLuminosity
}
// END MIX TILE
// mix luminous actor
for (actor in Terrarum.game.actorContainer) {
if (actor is Luminous && actor is ActorWithBody) {
val tileX = Math.round(actor.hitbox!!.pointedX / TSIZE)
val tileY = Math.round(actor.hitbox!!.pointedY / TSIZE) - 1
val actorLuminosity = actor.luminosity
if (x == tileX && y == tileY) {
lightLevelThis = maximiseRGB(lightLevelThis, actorLuminosity) // maximise to not exceed 1.0 with normal (<= 1.0) light
break
}
}
}
if (!doNotCalculateAmbient) {
// calculate ambient
var ambient: Int = 0
var nearby: Int = 0
for (yoff in -1..1) {
for (xoff in -1..1) {
/**
* filter for 'v's as:
* +-+-+-+
* |a|v|a|
* +-+-+-+
* |v| |v|
* +-+-+-+
* |a|v|a|
* +-+-+-+
*/
if (xoff != yoff && -xoff != yoff) {
// 'v' tiles
if (!outOfMapBounds(x + xoff, y + yoff)) {
nearby = getLight(x + xoff, y + yoff) ?: 0
}
}
else if (xoff != 0 && yoff != 0) {
// 'a' tiles
if (!outOfMapBounds(x + xoff, y + yoff)) {
nearby = darkenUniformInt(getLight(x + xoff, y + yoff) ?: 0, 12) //2 for 40step
// mix some to have more 'spreading'
// so that light spreads in a shape of an octagon instead of a diamond
}
}
else {
nearby = 0 // exclude 'me' tile
}
ambient = maximiseRGB(ambient, nearby) // keep base value as brightest nearby
}
}
ambient = darkenColoured(ambient,
thisTileOpacity) // get real ambient by appling opacity value
// mix and return lightlevel and ambient
return maximiseRGB(lightLevelThis, ambient)
}
else {
return lightLevelThis
}
}
fun draw(g: Graphics) {
val for_x_start = MapCamera.cameraX / TSIZE - 1 // fix for premature lightmap rendering
val for_y_start = MapCamera.cameraY / TSIZE - 1 // on topmost/leftmost side
val for_x_end = for_x_start + MapCamera.getRenderWidth() / TSIZE + 3
val for_y_end = for_y_start + MapCamera.getRenderHeight() / TSIZE + 2 // same fix as above
// draw
try {
// loop for "scanlines"
for (y in for_y_start..for_y_end) {
// loop x
var x = for_x_start
while (x < for_x_end) {
// smoothing enabled
if (Terrarum.game.screenZoom >= 1
&& Terrarum.gameConfig.getAsBoolean("smoothlighting") ?: false) {
val thisLightLevel = getLight(x, y) ?: 0
if (x < for_x_end && thisLightLevel == 0
&& getLight(x, y - 1) == 0) {
try {
// coalesce zero intensity blocks to one
var zeroLevelCounter = 1
while (getLight(x + zeroLevelCounter, y) == 0) {
zeroLevelCounter += 1
if (x + zeroLevelCounter >= for_x_end) break
}
g.color = Color(0)
g.fillRect(
(x.toFloat() * TSIZE.toFloat() * Terrarum.game.screenZoom).round().toFloat(),
(y.toFloat() * TSIZE.toFloat() * Terrarum.game.screenZoom).round().toFloat(),
((TSIZE * Terrarum.game.screenZoom).ceil() * zeroLevelCounter).toFloat(),
(TSIZE * Terrarum.game.screenZoom).ceil().toFloat()
)
x += zeroLevelCounter - 1
}
catch (e: ArrayIndexOutOfBoundsException) {
// do nothing
}
}
else {
/** a
* +-+-+
* |i|j|
* b +-+-+ c
* |k|l|
* +-+-+
* d
*/
val a = maximiseRGB(
thisLightLevel,
getLight(x, y - 1) ?: thisLightLevel
)
val d = maximiseRGB(
thisLightLevel,
getLight(x, y + 1) ?: thisLightLevel
)
val b = maximiseRGB(
thisLightLevel,
getLight(x - 1, y) ?: thisLightLevel
)
val c = maximiseRGB(
thisLightLevel,
getLight(x + 1, y) ?: thisLightLevel
)
val colourMapItoL = IntArray(4)
colourMapItoL[0] = colourLinearMix(a, b)
colourMapItoL[1] = colourLinearMix(a, c)
colourMapItoL[2] = colourLinearMix(b, d)
colourMapItoL[3] = colourLinearMix(c, d)
for (iy in 0..1) {
for (ix in 0..1) {
g.color = Color(colourMapItoL[iy * 2 + ix].rgb30ClampTo24())
g.fillRect(
(x.toFloat() * TSIZE.toFloat() * Terrarum.game.screenZoom).round()
+ ix * TSIZE / 2 * Terrarum.game.screenZoom,
(y.toFloat() * TSIZE.toFloat() * Terrarum.game.screenZoom).round()
+ iy * TSIZE / 2 * Terrarum.game.screenZoom,
(TSIZE * Terrarum.game.screenZoom / 2).ceil().toFloat(),
(TSIZE * Terrarum.game.screenZoom / 2).ceil().toFloat()
)
}
}
}
}
// smoothing disabled
else {
try {
val thisLightLevel = getLight(x, y)
// coalesce identical intensity blocks to one
var sameLevelCounter = 1
while (getLight(x + sameLevelCounter, y) == thisLightLevel) {
sameLevelCounter += 1
if (x + sameLevelCounter >= for_x_end) break
}
g.color = Color((getLight(x, y) ?: 0).rgb30ClampTo24())
g.fillRect(
(x.toFloat() * TSIZE.toFloat() * Terrarum.game.screenZoom).round().toFloat(),
(y.toFloat() * TSIZE.toFloat() * Terrarum.game.screenZoom).round().toFloat(),
((TSIZE * Terrarum.game.screenZoom).ceil() * sameLevelCounter).toFloat(),
(TSIZE * Terrarum.game.screenZoom).ceil().toFloat()
)
x += sameLevelCounter - 1
}
catch (e: ArrayIndexOutOfBoundsException) {
// do nothing
}
}
x++
}
}
}
catch (e: ArrayIndexOutOfBoundsException) {
}
}
/**
* Subtract each channel's RGB value.
*
* It works like:
*
* f(data, darken) = RGB(data.r - darken.r, data.g - darken.g, data.b - darken.b)
*
* @param data Raw channel value (0-39) per channel
* @param darken (0-39) per channel
* @return darkened data (0-39) per channel
*/
fun darkenColoured(data: Int, darken: Int): Int {
if (darken.toInt() < 0 || darken.toInt() >= COLOUR_RANGE_SIZE)
throw IllegalArgumentException("darken: out of range ($darken)")
var r = data.r() * (1f - darken.r() * 6) // 6: Arbitrary value
var g = data.g() * (1f - darken.g() * 6) // TODO gamma correction?
var b = data.b() * (1f - darken.b() * 6)
return constructRGBFromFloat(r.clampZero(), g.clampZero(), b.clampZero())
}
/**
* Darken each channel by 'darken' argument
*
* It works like:
*
* f(data, darken) = RGB(data.r - darken, data.g - darken, data.b - darken)
* @param data (0-39) per channel
* @param darken (0-39)
* @return
*/
fun darkenUniformInt(data: Int, darken: Int): Int {
if (darken < 0 || darken > CHANNEL_MAX)
throw IllegalArgumentException("darken: out of range ($darken)")
val darkenColoured = constructRGBFromInt(darken, darken, darken)
return darkenColoured(data, darkenColoured)
}
/** Get each channel from two RGB values, return new RGB that has max value of each channel
* @param rgb
* *
* @param rgb2
* *
* @return
*/
private fun maximiseRGB(rgb: Int, rgb2: Int): Int {
val r1 = rgb.rawR()
val r2 = rgb2.rawR()
val newR = if (r1 > r2) r1 else r2
val g1 = rgb.rawG()
val g2 = rgb2.rawG()
val newG = if (g1 > g2) g1 else g2
val b1 = rgb.rawB()
val b2 = rgb2.rawB()
val newB = if (b1 > b2) b1 else b2
return constructRGBFromInt(newR, newG, newB)
}
private fun screenBlend(rgb: Int, rgb2: Int): Int {
val r1 = rgb.r()
val r2 = rgb2.r()
val newR = 1 - (1 - r1) * (1 - r2)
val g1 = rgb.g()
val g2 = rgb2.g()
val newG = 1 - (1 - g1) * (1 - g2)
val b1 = rgb.b()
val b2 = rgb2.b()
val newB = 1 - (1 - b1) * (1 - b2)
return constructRGBFromFloat(newR, newG, newB)
}
fun Int.rawR() = this / MUL_2
fun Int.rawG() = this % MUL_2 / MUL
fun Int.rawB() = this % MUL
fun Int.r(): Float = this.rawR() / CHANNEL_MAX_FLOAT
fun Int.g(): Float = this.rawG() / CHANNEL_MAX_FLOAT
fun Int.b(): Float = this.rawB() / CHANNEL_MAX_FLOAT
/**
* @param RGB
* *
* @param offset 2 = R, 1 = G, 0 = B
* *
* @return
*/
fun getRaw(RGB: Int, offset: Int): Int {
if (offset == OFFSET_R) return RGB.rawR()
else if (offset == OFFSET_G) return RGB.rawG()
else if (offset == OFFSET_B) return RGB.rawB()
else throw IllegalArgumentException("Channel offset out of range")
}
private fun addRaw(rgb1: Int, rgb2: Int): Int {
val newR = (rgb1.rawR() + rgb2.rawR()).clampChannel()
val newG = (rgb1.rawG() + rgb2.rawG()).clampChannel()
val newB = (rgb1.rawB() + rgb2.rawB()).clampChannel()
return constructRGBFromInt(newR, newG, newB)
}
fun constructRGBFromInt(r: Int, g: Int, b: Int): Int {
if (r !in 0..CHANNEL_MAX) throw IllegalArgumentException("Red: out of range ($r)")
if (g !in 0..CHANNEL_MAX) throw IllegalArgumentException("Green: out of range ($g)")
if (b !in 0..CHANNEL_MAX) throw IllegalArgumentException("Blue: out of range ($b)")
return (r * MUL_2 + g * MUL + b)
}
fun constructRGBFromFloat(r: Float, g: Float, b: Float): Int {
if (r < 0 || r > 1.0f) throw IllegalArgumentException("Red: out of range ($r)")
if (g < 0 || g > 1.0f) throw IllegalArgumentException("Green: out of range ($g)")
if (b < 0 || b > 1.0f) throw IllegalArgumentException("Blue: out of range ($b)")
val intR = (r * CHANNEL_MAX).floor()
val intG = (g * CHANNEL_MAX).floor()
val intB = (b * CHANNEL_MAX).floor()
return constructRGBFromInt(intR, intG, intB)
}
private fun colourLinearMix(colA: Int, colB: Int): Int {
val r = (colA.rawR() + colB.rawR()) ushr 1
val g = (colA.rawG() + colB.rawG()) ushr 1
val b = (colA.rawB() + colB.rawB()) ushr 1
return constructRGBFromInt(r, g, b)
}
private fun outOfBounds(x: Int, y: Int): Boolean =
x !in 0..Terrarum.game.map.width - 1 || y !in 0..Terrarum.game.map.height - 1
private fun outOfMapBounds(x: Int, y: Int): Boolean =
//x !in 0..lightMapMSB!![0].size - 1 || y !in 0..lightMapMSB!!.size - 1
x !in 0..lightmap[0].size - 1 || y !in 0..lightmap.size - 1
private fun Int.clampZero() = if (this < 0) 0 else this
private fun Float.clampZero() = if (this < 0) 0f else this
private fun Int.clampChannel() = if (this < 0) 0 else if (this > CHANNEL_MAX) CHANNEL_MAX else this
private fun Float.clampOne() = if (this < 0) 0f else if (this > 1) 1f else this
fun getValueFromMap(x: Int, y: Int): Int? = getLight(x, y)
private fun purgePartOfLightmap(x1: Int, y1: Int, x2: Int, y2: Int) {
try {
for (y in y1 - 1..y2 + 1) {
for (x in x1 - 1..x2 + 1) {
//if (y == y1 - 1 || y == y2 + 1 || x == x1 - 1 || x == x2 + 1) {
// fill the rim with (pre) calculation
// setLight(x, y, preCalculateUpdateGLOnly(x, y))
//}
//else {
setLight(x, y, 0)
//}
}
}
}
catch (e: ArrayIndexOutOfBoundsException) {
}
}
private fun clampWTile(x: Int): Int {
if (x < 0) {
return 0
}
else if (x > Terrarum.game.map.width) {
return Terrarum.game.map.width
}
else {
return x
}
}
private fun clampHTile(x: Int): Int {
if (x < 0) {
return 0
}
else if (x > Terrarum.game.map.height) {
return Terrarum.game.map.height
}
else {
return x
}
}
private fun arithmeticAverage(vararg i: Int): Int {
var sum = 0
for (k in i.indices) {
sum += i[k]
}
return Math.round(sum / i.size.toFloat())
}
internal data class LightmapLantern(
var x: Int,
var y: Int,
var intensity: Int
)
private fun Int.clamp256() = if (this > 255) 255 else this
fun Int.rgb30ClampTo24(): Int {
val r = this.rawR().clamp256()
val g = this.rawG().clamp256()
val b = this.rawB().clamp256()
return r.shl(16) or g.shl(8) or b
}
infix fun Float.powerOf(f: Float) = FastMath.pow(this, f)
private fun Float.sqr() = this * this
private fun Float.sqrt() = FastMath.sqrt(this)
fun Float.floor() = FastMath.floor(this)
fun Float.round(): Int = Math.round(this)
fun Float.ceil() = FastMath.ceil(this)
}

View File

@@ -0,0 +1,489 @@
package net.torvald.terrarum.mapdrawer
import net.torvald.terrarum.gamemap.GameMap
import net.torvald.terrarum.gamemap.PairedMapLayer
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.tileproperties.TileNameCode
import net.torvald.terrarum.tileproperties.TilePropCodex
import com.jme3.math.FastMath
import net.torvald.terrarum.setBlendMul
import net.torvald.terrarum.setBlendNormal
import org.lwjgl.opengl.GL11
import org.newdawn.slick.GameContainer
import org.newdawn.slick.Graphics
import org.newdawn.slick.SlickException
import org.newdawn.slick.SpriteSheet
import java.util.*
/**
* Created by minjaesong on 16-01-19.
*/
object MapCamera {
private val map: GameMap = Terrarum.game.map;
var cameraX = 0
private set
var cameraY = 0
private set
private val TSIZE = MapDrawer.TILE_SIZE
private var tilesWall: SpriteSheet = SpriteSheet("./res/graphics/terrain/wall.png", TSIZE, TSIZE)
private var tilesTerrain: SpriteSheet = SpriteSheet("./res/graphics/terrain/terrain.png", TSIZE, TSIZE)
private var tilesWire: SpriteSheet = SpriteSheet("./res/graphics/terrain/wire.png", TSIZE, TSIZE)
private var tilesetBook: Array<SpriteSheet> = arrayOf(tilesWall, tilesTerrain, tilesWire)
private val WALL = GameMap.WALL
private val TERRAIN = GameMap.TERRAIN
private val WIRE = GameMap.WIRE
private var renderWidth: Int = 0
private var renderHeight: Int = 0
private val NEARBY_TILE_KEY_UP = 0
private val NEARBY_TILE_KEY_RIGHT = 1
private val NEARBY_TILE_KEY_DOWN = 2
private val NEARBY_TILE_KEY_LEFT = 3
private val NEARBY_TILE_CODE_UP = 1
private val NEARBY_TILE_CODE_RIGHT = 2
private val NEARBY_TILE_CODE_DOWN = 4
private val NEARBY_TILE_CODE_LEFT = 8
/**
* Connectivity group 01 : artificial tiles
* It holds different shading rule to discriminate with group 02, index 0 is single tile.
* These are the tiles that only connects to itself, will not connect to colour variants
*/
private val TILES_CONNECT_SELF = arrayOf(
TileNameCode.ICE_MAGICAL
, TileNameCode.ILLUMINATOR_BLACK
, TileNameCode.ILLUMINATOR_BLUE
, TileNameCode.ILLUMINATOR_BROWN
, TileNameCode.ILLUMINATOR_CYAN
, TileNameCode.ILLUMINATOR_FUCHSIA
, TileNameCode.ILLUMINATOR_GREEN
, TileNameCode.ILLUMINATOR_GREEN_DARK
, TileNameCode.ILLUMINATOR_GREY_DARK
, TileNameCode.ILLUMINATOR_GREY_LIGHT
, TileNameCode.ILLUMINATOR_GREY_MED
, TileNameCode.ILLUMINATOR_ORANGE
, TileNameCode.ILLUMINATOR_PURPLE
, TileNameCode.ILLUMINATOR_RED
, TileNameCode.ILLUMINATOR_TAN
, TileNameCode.ILLUMINATOR_WHITE
, TileNameCode.ILLUMINATOR_YELLOW
, TileNameCode.ILLUMINATOR_BLACK_OFF
, TileNameCode.ILLUMINATOR_BLUE_OFF
, TileNameCode.ILLUMINATOR_BROWN_OFF
, TileNameCode.ILLUMINATOR_CYAN_OFF
, TileNameCode.ILLUMINATOR_FUCHSIA_OFF
, TileNameCode.ILLUMINATOR_GREEN_OFF
, TileNameCode.ILLUMINATOR_GREEN_DARK_OFF
, TileNameCode.ILLUMINATOR_GREY_DARK_OFF
, TileNameCode.ILLUMINATOR_GREY_LIGHT_OFF
, TileNameCode.ILLUMINATOR_GREY_MED_OFF
, TileNameCode.ILLUMINATOR_ORANGE_OFF
, TileNameCode.ILLUMINATOR_PURPLE_OFF
, TileNameCode.ILLUMINATOR_RED_OFF
, TileNameCode.ILLUMINATOR_TAN_OFF
, TileNameCode.ILLUMINATOR_WHITE_OFF
, TileNameCode.ILLUMINATOR_YELLOW
, TileNameCode.SANDSTONE
, TileNameCode.SANDSTONE_BLACK
, TileNameCode.SANDSTONE_DESERT
, TileNameCode.SANDSTONE_RED
, TileNameCode.SANDSTONE_WHITE
, TileNameCode.SANDSTONE_GREEN
)
/**
* Connectivity group 02 : natural tiles
* It holds different shading rule to discriminate with group 01, index 0 is middle tile.
*/
private val TILES_CONNECT_MUTUAL = arrayOf(
TileNameCode.STONE
, TileNameCode.DIRT
, TileNameCode.GRASS
, TileNameCode.PLANK_BIRCH
, TileNameCode.PLANK_BLOODROSE
, TileNameCode.PLANK_EBONY
, TileNameCode.PLANK_NORMAL
, TileNameCode.SAND
, TileNameCode.SAND_WHITE
, TileNameCode.SAND_RED
, TileNameCode.SAND_DESERT
, TileNameCode.SAND_BLACK
, TileNameCode.SAND_GREEN
, TileNameCode.GRAVEL
, TileNameCode.GRAVEL_GREY
, TileNameCode.SNOW
, TileNameCode.ICE_NATURAL
, TileNameCode.ORE_COPPER
, TileNameCode.ORE_IRON
, TileNameCode.ORE_GOLD
, TileNameCode.ORE_SILVER
, TileNameCode.ORE_ILMENITE
, TileNameCode.ORE_AURICHALCUM
, TileNameCode.WATER
, TileNameCode.WATER_1
, TileNameCode.WATER_2
, TileNameCode.WATER_3
, TileNameCode.WATER_4
, TileNameCode.WATER_5
, TileNameCode.WATER_6
, TileNameCode.WATER_7
, TileNameCode.WATER_8
, TileNameCode.WATER_9
, TileNameCode.WATER_10
, TileNameCode.WATER_11
, TileNameCode.WATER_12
, TileNameCode.WATER_13
, TileNameCode.WATER_14
, TileNameCode.WATER_15
, TileNameCode.LAVA
, TileNameCode.LAVA_1
, TileNameCode.LAVA_2
, TileNameCode.LAVA_3
, TileNameCode.LAVA_4
, TileNameCode.LAVA_5
, TileNameCode.LAVA_6
, TileNameCode.LAVA_7
, TileNameCode.LAVA_8
, TileNameCode.LAVA_9
, TileNameCode.LAVA_10
, TileNameCode.LAVA_11
, TileNameCode.LAVA_12
, TileNameCode.LAVA_13
, TileNameCode.LAVA_14
, TileNameCode.LAVA_15
)
/**
* Torches, levers, switches, ...
*/
private val TILES_WALL_STICKER = arrayOf(
TileNameCode.TORCH
, TileNameCode.TORCH_FROST
, TileNameCode.TORCH_OFF
, TileNameCode.TORCH_FROST_OFF
)
/**
* platforms, ...
*/
private val TILES_WALL_STICKER_CONNECT_SELF = arrayOf<Int>(
)
/**
* Tiles that half-transparent and has hue
* will blend colour using colour multiplication
* i.e. red hues get lost if you dive into the water
*/
private val TILES_BLEND_MUL = arrayOf(
TileNameCode.WATER
, TileNameCode.WATER_1
, TileNameCode.WATER_2
, TileNameCode.WATER_3
, TileNameCode.WATER_4
, TileNameCode.WATER_5
, TileNameCode.WATER_6
, TileNameCode.WATER_7
, TileNameCode.WATER_8
, TileNameCode.WATER_9
, TileNameCode.WATER_10
, TileNameCode.WATER_11
, TileNameCode.WATER_12
, TileNameCode.WATER_13
, TileNameCode.WATER_14
, TileNameCode.WATER_15
, TileNameCode.LAVA
, TileNameCode.LAVA_1
, TileNameCode.LAVA_2
, TileNameCode.LAVA_3
, TileNameCode.LAVA_4
, TileNameCode.LAVA_5
, TileNameCode.LAVA_6
, TileNameCode.LAVA_7
, TileNameCode.LAVA_8
, TileNameCode.LAVA_9
, TileNameCode.LAVA_10
, TileNameCode.LAVA_11
, TileNameCode.LAVA_12
, TileNameCode.LAVA_13
, TileNameCode.LAVA_14
, TileNameCode.LAVA_15
)
fun update(gc: GameContainer, delta_t: Int) {
val player = Terrarum.game.player
renderWidth = FastMath.ceil(Terrarum.WIDTH / Terrarum.game.screenZoom) // div, not mul
renderHeight = FastMath.ceil(Terrarum.HEIGHT / Terrarum.game.screenZoom)
// position - (WH / 2)
cameraX = Math.round(FastMath.clamp(
player.hitbox!!.centeredX - renderWidth / 2, TSIZE.toFloat(), map.width * TSIZE - renderWidth - TSIZE.toFloat()))
cameraY = Math.round(FastMath.clamp(
player.hitbox!!.centeredY - renderHeight / 2, TSIZE.toFloat(), map.height * TSIZE - renderHeight - TSIZE.toFloat()))
}
fun renderBehind(gc: GameContainer, g: Graphics) {
/**
* render to camera
*/
setBlendNormal()
drawTiles(WALL, false)
drawTiles(TERRAIN, false)
}
fun renderFront(gc: GameContainer, g: Graphics) {
setBlendMul()
drawTiles(TERRAIN, true)
setBlendNormal()
}
private fun drawTiles(mode: Int, drawModeTilesBlendMul: Boolean) {
val for_y_start = div16(cameraY)
val for_x_start = div16(cameraX)
val for_y_end = clampHTile(for_y_start + div16(renderHeight) + 2)
val for_x_end = clampWTile(for_x_start + div16(renderWidth) + 2)
// initialise
tilesetBook[mode].startUse()
// loop
for (y in for_y_start..for_y_end - 1) {
for (x in for_x_start..for_x_end - 1) {
val thisTile: Int?
if (mode % 3 == WALL)
thisTile = map.getTileFromWall(x, y)
else if (mode % 3 == TERRAIN)
thisTile = map.getTileFromTerrain(x, y)
else if (mode % 3 == WIRE)
thisTile = map.getTileFromWire(x, y)
else
throw IllegalArgumentException()
val noDamageLayer = mode % 3 == WIRE
// draw
try {
if (
(mode == WALL || mode == TERRAIN) // not an air tile
&& (thisTile ?: 0) > 0
&&
// check if light level of nearby or this tile is illuminated
( LightmapRenderer.getValueFromMap(x, y) ?: 0 > 0
|| LightmapRenderer.getValueFromMap(x - 1, y) ?: 0 > 0
|| LightmapRenderer.getValueFromMap(x + 1, y) ?: 0 > 0
|| LightmapRenderer.getValueFromMap(x, y - 1) ?: 0 > 0
|| LightmapRenderer.getValueFromMap(x, y + 1) ?: 0 > 0
|| LightmapRenderer.getValueFromMap(x - 1, y - 1) ?: 0 > 0
|| LightmapRenderer.getValueFromMap(x + 1, y + 1) ?: 0 > 0
|| LightmapRenderer.getValueFromMap(x + 1, y - 1) ?: 0 > 0
|| LightmapRenderer.getValueFromMap(x - 1, y + 1) ?: 0 > 0)
) {
val nearbyTilesInfo: Int
if (isWallSticker(thisTile)) {
nearbyTilesInfo = getNearbyTilesInfoWallSticker(x, y)
} else if (isConnectMutual(thisTile)) {
nearbyTilesInfo = getNearbyTilesInfoNonSolid(x, y, mode)
} else if (isConnectSelf(thisTile)) {
nearbyTilesInfo = getNearbyTilesInfo(x, y, mode, thisTile)
} else {
nearbyTilesInfo = 0
}
val thisTileX: Int
if (!noDamageLayer)
thisTileX = PairedMapLayer.RANGE * ((thisTile ?: 0) % PairedMapLayer.RANGE) + nearbyTilesInfo
else
thisTileX = nearbyTilesInfo
val thisTileY = (thisTile ?: 0) / PairedMapLayer.RANGE
if (drawModeTilesBlendMul) {
if (isBlendMul(thisTile)) {
drawTile(mode, x, y, thisTileX, thisTileY)
}
} else {
// do NOT add "if (!isBlendMul(thisTile))"!
// or else they will not look like they should be when backed with wall
drawTile(mode, x, y, thisTileX, thisTileY)
}
}
} catch (e: NullPointerException) {
// do nothing. This exception handling may hide erratic behaviour completely.
}
}
}
tilesetBook[mode].endUse()
}
private fun getGrassInfo(x: Int, y: Int, from: Int, to: Int): Int {
return 0
}
/**
* @param x
* *
* @param y
* *
* @return [0-15] 1: up, 2: right, 4: down, 8: left
*/
private fun getNearbyTilesInfo(x: Int, y: Int, mode: Int, mark: Int?): Int {
val nearbyTiles = IntArray(4)
nearbyTiles[NEARBY_TILE_KEY_LEFT] = map.getTileFrom(mode, x - 1, y) ?: 4096
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = map.getTileFrom(mode, x + 1, y) ?: 4096
nearbyTiles[NEARBY_TILE_KEY_UP] = map.getTileFrom(mode, x, y - 1) ?: 4906
nearbyTiles[NEARBY_TILE_KEY_DOWN] = map.getTileFrom(mode, x, y + 1) ?: 4096
// try for
var ret = 0
for (i in 0..3) {
if (nearbyTiles[i] == mark) {
ret += 1 shl i // add 1, 2, 4, 8 for i = 0, 1, 2, 3
}
}
return ret
}
private fun getNearbyTilesInfoNonSolid(x: Int, y: Int, mode: Int): Int {
val nearbyTiles = IntArray(4)
nearbyTiles[NEARBY_TILE_KEY_LEFT] = map.getTileFrom(mode, x - 1, y) ?: 4096
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = map.getTileFrom(mode, x + 1, y) ?: 4096
nearbyTiles[NEARBY_TILE_KEY_UP] = map.getTileFrom(mode, x, y - 1) ?: 4096
nearbyTiles[NEARBY_TILE_KEY_DOWN] = map.getTileFrom(mode, x, y + 1) ?: 4096
// try for
var ret = 0
for (i in 0..3) {
try {
if (!TilePropCodex.getProp(nearbyTiles[i]).isSolid) {
ret += (1 shl i) // add 1, 2, 4, 8 for i = 0, 1, 2, 3
}
} catch (e: ArrayIndexOutOfBoundsException) {
}
}
return ret
}
private fun getNearbyTilesInfoWallSticker(x: Int, y: Int): Int {
val nearbyTiles = IntArray(4)
val NEARBY_TILE_KEY_BACK = NEARBY_TILE_KEY_UP
nearbyTiles[NEARBY_TILE_KEY_LEFT] = map.getTileFrom(TERRAIN, x - 1, y) ?: 4096
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = map.getTileFrom(TERRAIN, x + 1, y) ?: 4096
nearbyTiles[NEARBY_TILE_KEY_DOWN] = map.getTileFrom(TERRAIN, x, y + 1) ?: 4096
nearbyTiles[NEARBY_TILE_KEY_BACK] = map.getTileFrom(WALL, x, y) ?: 4096
try {
if (TilePropCodex.getProp(nearbyTiles[NEARBY_TILE_KEY_RIGHT]).isSolid
&& TilePropCodex.getProp(nearbyTiles[NEARBY_TILE_KEY_LEFT]).isSolid) {
if (TilePropCodex.getProp(nearbyTiles[NEARBY_TILE_KEY_BACK]).isSolid)
return 0
else
return 3
} else if (TilePropCodex.getProp(nearbyTiles[NEARBY_TILE_KEY_RIGHT]).isSolid) {
return 2
} else if (TilePropCodex.getProp(nearbyTiles[NEARBY_TILE_KEY_LEFT]).isSolid) {
return 1
} else if (TilePropCodex.getProp(nearbyTiles[NEARBY_TILE_KEY_BACK]).isSolid) {
return 0
} else
return 3
} catch (e: ArrayIndexOutOfBoundsException) {
return if (TilePropCodex.getProp(nearbyTiles[NEARBY_TILE_KEY_BACK]).isSolid)
0
else
3
}
}
private fun drawTile(mode: Int, tilewisePosX: Int, tilewisePosY: Int, sheetX: Int, sheetY: Int) {
if (Terrarum.game.screenZoom == 1f) {
tilesetBook[mode].renderInUse(
FastMath.floor((tilewisePosX * TSIZE).toFloat()), FastMath.floor((tilewisePosY * TSIZE).toFloat()), sheetX, sheetY)
} else {
tilesetBook[mode].getSprite(
sheetX, sheetY).drawEmbedded(
Math.round(tilewisePosX.toFloat() * TSIZE.toFloat() * Terrarum.game.screenZoom).toFloat(), Math.round(tilewisePosY.toFloat() * TSIZE.toFloat() * Terrarum.game.screenZoom).toFloat(), FastMath.ceil(TSIZE * Terrarum.game.screenZoom).toFloat(), FastMath.ceil(TSIZE * Terrarum.game.screenZoom).toFloat())
}
}
fun div16(x: Int): Int = x and 0x7FFFFFFF shr 4
fun mod16(x: Int): Int = x and 15
fun quantise16(x: Int): Int = x and 0xFFFFFFF0.toInt()
fun clampW(x: Int): Int {
if (x < 0) {
return 0
} else if (x > map.width * TSIZE) {
return map.width * TSIZE
} else {
return x
}
}
fun clampH(x: Int): Int {
if (x < 0) {
return 0
} else if (x > map.height * TSIZE) {
return map.height * TSIZE
} else {
return x
}
}
fun clampWTile(x: Int): Int {
if (x < 0) {
return 0
} else if (x > map.width) {
return map.width
} else {
return x
}
}
fun clampHTile(x: Int): Int {
if (x < 0) {
return 0
} else if (x > map.height) {
return map.height
} else {
return x
}
}
fun getRenderWidth(): Int = renderWidth
fun getRenderHeight(): Int = renderHeight
fun getRenderStartX(): Int = div16(cameraX)
fun getRenderStartY(): Int = div16(cameraY)
fun getRenderEndX(): Int = clampWTile(getRenderStartX() + div16(renderWidth) + 2)
fun getRenderEndY(): Int = clampHTile(getRenderStartY() + div16(renderHeight) + 2)
private fun isConnectSelf(b: Int?): Boolean = TILES_CONNECT_SELF.contains(b)
private fun isConnectMutual(b: Int?): Boolean = TILES_CONNECT_MUTUAL.contains(b)
private fun isWallSticker(b: Int?): Boolean = TILES_WALL_STICKER.contains(b)
private fun isPlatform(b: Int?): Boolean = TILES_WALL_STICKER_CONNECT_SELF.contains(b)
private fun isBlendMul(b: Int?): Boolean = TILES_BLEND_MUL.contains(b)
}

View File

@@ -0,0 +1,90 @@
package net.torvald.terrarum.mapdrawer
import net.torvald.terrarum.gamemap.GameMap
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.tileproperties.TileNameCode
import net.torvald.terrarum.tilestats.TileStats
import com.jme3.math.FastMath
import org.newdawn.slick.*
/**
* Created by minjaesong on 15-12-31.
*/
object MapDrawer {
const val TILE_SIZE = 16
private var envOverlayColourmap: Image = Image("./res/graphics/black_body_col_1000_40000_K.png")
private val ENV_COLTEMP_LOWEST = 5500
private val ENV_COLTEMP_HIGHEST = 7500
val ENV_COLTEMP_NOON = 6500
private var colTemp: Int = 0
private val TILES_COLD = intArrayOf(
TileNameCode.ICE_MAGICAL
, TileNameCode.ICE_FRAGILE
, TileNameCode.ICE_NATURAL
, TileNameCode.SNOW)
private val TILES_WARM = intArrayOf(
TileNameCode.SAND_DESERT
, TileNameCode.SAND_RED)
@JvmStatic
fun update(gc: GameContainer, delta_t: Int) {
}
@JvmStatic
fun render(gc: GameContainer, g: Graphics) {
}
@JvmStatic
fun drawEnvOverlay(g: Graphics) {
val onscreen_tiles_max = FastMath.ceil(Terrarum.HEIGHT * Terrarum.WIDTH / FastMath.sqr(TILE_SIZE.toFloat())) * 2
val onscreen_tiles_cap = onscreen_tiles_max / 4f
val onscreen_cold_tiles = TileStats.getCount(*TILES_COLD).toFloat()
val onscreen_warm_tiles = TileStats.getCount(*TILES_WARM).toFloat()
val colTemp_cold = colTempLinearFunc(onscreen_cold_tiles / onscreen_tiles_cap)
val colTemp_warm = colTempLinearFunc(-(onscreen_warm_tiles / onscreen_tiles_cap))
colTemp = colTemp_warm + colTemp_cold - ENV_COLTEMP_NOON
val zoom = Terrarum.game.screenZoom
g.color = getColourFromMap(colTemp)
//g.color = getColourFromMap(3022)
g.fillRect(
MapCamera.cameraX * zoom,
MapCamera.cameraY * zoom,
Terrarum.WIDTH * if (zoom < 1) 1f / zoom else zoom,
Terrarum.HEIGHT * if (zoom < 1) 1f / zoom else zoom
)
}
/**
* @param x [-1 , 1], 0 for 6500K (median of ENV_COLTEMP_HIGHEST and ENV_COLTEMP_LOWEST)
* *
* @return
*/
private fun colTempLinearFunc(x: Float): Int {
val colTempMedian = (ENV_COLTEMP_HIGHEST + ENV_COLTEMP_LOWEST) / 2
return Math.round((ENV_COLTEMP_HIGHEST - ENV_COLTEMP_LOWEST) / 2 * FastMath.clamp(x, -1f, 1f) + colTempMedian)
}
fun getColourFromMap(K: Int): Color {
return envOverlayColourmap.getColor(colTempToImagePos(K), 0)
}
private fun colTempToImagePos(K: Int): Int {
if (K < 1000 || K >= 40000) throw IllegalArgumentException("K: out of range. ($K)")
return (K - 1000) / 10
}
@JvmStatic
fun getColTemp(): Int {
return colTemp
}
}

View File

@@ -0,0 +1,65 @@
package net.torvald.terrarum.mapgenerator
import net.torvald.random.HQRNG
import com.sun.javaws.exceptions.InvalidArgumentException
import java.util.Random
object FloatingIslandsPreset {
val PRESETS = 5
internal fun generatePreset(random: HQRNG): Array<IntArray> {
val index = random.nextInt(PRESETS)
return generatePreset(index, random)
}
internal fun generatePreset(index: Int, random: Random): Array<IntArray> {
if (index == 0) {
return processPreset(random, FloatingIslePreset01.data, FloatingIslePreset01.w, FloatingIslePreset01.h)
}
else if (index == 1) {
return processPreset(random, FloatingIslePreset02.data, FloatingIslePreset02.w, FloatingIslePreset02.h)
}
else if (index == 2) {
return processPreset(random, FloatingIslePreset03.data, FloatingIslePreset03.w, FloatingIslePreset03.h)
}
else if (index == 3) {
return processPreset(random, FloatingIslePreset04.data, FloatingIslePreset04.w, FloatingIslePreset04.h)
}
else {
return processPreset(random, FloatingIslePreset05.data, FloatingIslePreset05.w, FloatingIslePreset05.h)
}
}
private fun processPreset(random: Random, preset: IntArray, w: Int, h: Int): Array<IntArray> {
val temp = Array(h) { IntArray(w) }
var counter = 0
val mirrored = random.nextBoolean()
for (i in 0..h - 1) {
for (j in 0..w - 1) {
if (!mirrored) {
if (counter < preset.size - 1) {
temp[i][j] = preset[counter]
counter++
}
else {
temp[i][j] = 0
}
}
else {
if (counter < preset.size - 1) {
temp[i][w - 1 - j] = preset[counter]
counter++
}
else {
temp[i][w - 1 - j] = 0
}
}
}
}
return temp
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,948 @@
package net.torvald.terrarum.mapgenerator
import net.torvald.random.HQRNG
import net.torvald.terrarum.gamemap.GameMap
import net.torvald.terrarum.tileproperties.TileNameCode
import com.jme3.math.FastMath
import com.sudoplay.joise.Joise
import com.sudoplay.joise.module.*
import java.util.*
object MapGenerator {
private lateinit var map: GameMap
private lateinit var random: Random
//private static float[] noiseArray;
var SEED: Long = 0
var WIDTH: Int = 0
var HEIGHT: Int = 0
//private lateinit var heightMap: IntArray
private lateinit var terrainMap: Array<BitSet>
var DIRT_LAYER_DEPTH: Int = 0
var TERRAIN_AVERAGE_HEIGHT: Int = 0
private var minimumFloatingIsleHeight: Int = 0
private val NOISE_GRAD_START = 0.67f
private val NOISE_GRAD_END = 0.56f
private val NOISE_SIMPLEX_ORE_START = 1.42f
private val NOISE_SIMPLEX_ORE_END = 1.28f
private val HILL_WIDTH = 256 // power of two!
//private val MAX_HILL_HEIGHT = 100
private val TERRAIN_UNDULATION = 250
private val SIMPLEXGEN_LARGEST_FEATURE = 200
private var OCEAN_WIDTH = 400
private var SHORE_WIDTH = 120
private val MAX_OCEAN_DEPTH = 200
private var GLACIER_MOUNTAIN_WIDTH = 900
private val GLACIER_MOUNTAIN_HEIGHT = 300
private val CAVEGEN_THRE_START = 0.95f
private val CAVEGEN_THRE_END = 0.67f
private var worldOceanPosition: Int = -1
private val TYPE_OCEAN_LEFT = 0
private val TYPE_OCEAN_RIGHT = 1
private val GRASSCUR_UP = 0
private val GRASSCUR_RIGHT = 1
private val GRASSCUR_DOWN = 2
private val GRASSCUR_LEFT = 3
private val TILE_MACRO_ALL = -1
fun attachMap(map: GameMap) {
this.map = map
WIDTH = map.width
HEIGHT = map.height
val widthMulFactor = WIDTH / 8192f
DIRT_LAYER_DEPTH = (100 * HEIGHT / 1024f).toInt()
minimumFloatingIsleHeight = (25 * (HEIGHT / 1024f)).toInt()
TERRAIN_AVERAGE_HEIGHT = HEIGHT / 4
OCEAN_WIDTH = Math.round(OCEAN_WIDTH * widthMulFactor)
SHORE_WIDTH = Math.round(SHORE_WIDTH * widthMulFactor)
GLACIER_MOUNTAIN_WIDTH = Math.round(GLACIER_MOUNTAIN_WIDTH * widthMulFactor)
}
/**
* Generate terrain and override attached map
*/
fun generateMap() {
random = HQRNG(SEED)
println("[mapgenerator] Seed: " + SEED)
worldOceanPosition = if (random.nextBoolean()) TYPE_OCEAN_LEFT else TYPE_OCEAN_RIGHT
//heightMap = raise2(MAX_HILL_HEIGHT / 2)
//generateOcean(heightMap)
//placeGlacierMount(heightMap)
//heightMapToObjectMap(heightMap)
terrainMap = raise3()
fillMapByNoiseMap()
/**
* Done: more perturbed overworld (harder to supra-navigate)
* Todo: veined ore distribution (metals) -- use veined simplex noise
* Todo: clustered gem distribution (clusters: [Ruby, Sapphire], Amethyst, Yellow topaz, emerald, diamond) -- use regular simplex noise
* Todo: Lakes! Aquifers! Lava chambers!
* Todo: deserts (variants: SAND_DESERT, SAND_RED)
* Todo: volcano(es?)
* Done: variants of beach (SAND, SAND_BEACH, SAND_BLACK, SAND_GREEN)
*/
val noiseArray = arrayOf(
TaggedJoise("Carving caves", noiseRidged(1.7f, 1.4f), 1f, TILE_MACRO_ALL, TILE_MACRO_ALL, TileNameCode.AIR, NoiseFilterSqrt, CAVEGEN_THRE_START, CAVEGEN_THRE_END)
, TaggedJoise("Collapsing caves", noiseBlobs(0.5f, 0.5f), 0.3f, TileNameCode.AIR, TileNameCode.STONE, TileNameCode.STONE, NoiseFilterUniform)
//, TaggedJoise("Putting stone patches on the ground", noiseBlobs(0.8f, 0.8f), 1.02f, TileNameCode.DIRT, TileNameCode.DIRT, TileNameCode.STONE, NoiseFilterQuadratic, noiseGradientEnd, noiseGradientStart)
//, TaggedJoise("Placing dirt spots in the cave", noiseBlobs(0.5f, 0.5f), 0.98f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.DIRT, NoiseFilterQuadratic, noiseGradientEnd, noiseGradientStart)
//, TaggedJoise("Quarrying some stone into gravels", noiseBlobs(0.5f, 0.5f), 0.98f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.GRAVEL, NoiseFilterQuadratic, noiseGradientEnd, noiseGradientStart)
//, TaggedJoise("Growing copper veins", noiseRidged(1.7f, 1.7f), 1.68f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.ORE_COPPER)
//, TaggedJoise("Cutting copper veins", noiseBlobs(0.4f, 0.4f), 0.26f, TileNameCode.ORE_COPPER, TileNameCode.STONE, TileNameCode.STONE)
//, TaggedJoise("Growing iron veins", noiseRidged(1.7f, 1.7f), 1.68f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.ORE_IRON)
//, TaggedJoise("Cutting iron veins", noiseBlobs(0.7f, 0.7f), 0.26f, TileNameCode.ORE_IRON, TileNameCode.STONE, TileNameCode.STONE)
//, TaggedJoise("Growing silver veins", noiseRidged(1.7f, 1.7f), 1.71f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.ORE_SILVER)
//, TaggedJoise("Cutting silver veins", noiseBlobs(0.7f, 0.7f), 0.26f, TileNameCode.ORE_SILVER, TileNameCode.STONE, TileNameCode.STONE)
//, TaggedJoise("Growing gold veins", noiseRidged(1.7f, 1.7f), 1.73f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.ORE_GOLD)
//, TaggedJoise("Cutting gold veins", noiseBlobs(0.7f, 0.7f), 0.26f, TileNameCode.ORE_GOLD, TileNameCode.STONE, TileNameCode.STONE)
////, TaggedJoise("Growing topaz clusters", noiseBlobs(0.9f, 0.9f), 2f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.RAW_TOPAZ)
//, TaggedJoise("Growing aluminium oxide clusters", noiseBlobs(0.9f, 0.9f), 1.7f, TileNameCode.STONE, TileNameCode.STONE, intArrayOf(TileNameCode.RAW_RUBY, TileNameCode.RAW_SAPPHIRE))
//, TaggedJoise("Growing emerald clusters", noiseBlobs(0.9f, 0.9f), 1,7f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.RAW_EMERALD)
//, TaggedJoise("Growing hearts of white", noiseBlobs(0.9f, 0.9f), 1.83f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.RAW_DIAMOND)
//, TaggedJoise("Growing hearts of violet", noiseRidged(2.5f, 2.5f), 1.75f, TileNameCode.STONE, TileNameCode.STONE, TileNameCode.RAW_AMETHYST)
//, TaggedJoise("Cutting over-grown hearts", noiseBlobs(0.7f, 0.7f), 0.17f, TileNameCode.RAW_AMETHYST, TileNameCode.STONE, TileNameCode.STONE)
)
processNoiseLayers(noiseArray)
/** TODO Cobaltite, Ilmenite, Aurichalcum (and possibly pitchblende?) */
floodBottomLava()
// freeze()
// fillOcean()
plantGrass()
//post-process
generateFloatingIslands()
//wire layer
for (i in 0..HEIGHT - 1) {
for (j in 0..WIDTH - 1) {
map.wireArray[i][j] = 0
}
}
// Free some memories
System.gc()
}
/* 1. Raise */
private fun noiseRidged(xStretch: Float, yStretch: Float): Joise {
val ridged = ModuleFractal()
ridged.setType(ModuleFractal.FractalType.RIDGEMULTI)
ridged.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
ridged.setNumOctaves(4)
ridged.setFrequency(1.0)
ridged.seed = SEED xor random.nextLong()
val ridged_autocorrect = ModuleAutoCorrect()
ridged_autocorrect.setRange(0.0, 1.0)
ridged_autocorrect.setSource(ridged)
val ridged_scale = ModuleScaleDomain()
ridged_scale.setScaleX(xStretch.toDouble())
ridged_scale.setScaleY(yStretch.toDouble())
ridged_scale.setSource(ridged_autocorrect)
return Joise(ridged_scale)
}
private fun noiseBlobs(xStretch: Float, yStretch: Float): Joise {
val gradval = ModuleBasisFunction()
gradval.seed = SEED xor random.nextLong()
gradval.setType(ModuleBasisFunction.BasisType.GRADVAL)
gradval.setInterpolation(ModuleBasisFunction.InterpolationType.QUINTIC)
val gradval_scale = ModuleScaleDomain()
gradval_scale.setScaleX(1.0 / xStretch)
gradval_scale.setScaleY(1.0 / yStretch)
gradval_scale.setSource(gradval)
return Joise(gradval_scale)
}
/**
* Note:
* * Threshold 1.4 for rarer gem clusters, 1.35 for ores
*/
private fun noiseSimplex(xStretch: Float, yStretch: Float): Joise {
val simplex = ModuleFractal()
simplex.seed = SEED
simplex.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.SIMPLEX)
simplex.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.LINEAR)
simplex.setNumOctaves(2)
simplex.setFrequency(1.0)
val simplex_scale = ModuleScaleDomain()
simplex_scale.setScaleX(1.0 / xStretch)
simplex_scale.setScaleY(1.0 / yStretch)
simplex_scale.setSource(simplex)
return Joise(simplex_scale)
}
private fun generateOcean(noiseArrayLocal: IntArray): IntArray {
val oceanLeftP1 = noiseArrayLocal[OCEAN_WIDTH]
val oceanRightP1 = noiseArrayLocal[noiseArrayLocal.size - OCEAN_WIDTH]
/**
* Add ocean so that:
* +1| - -
* 0| - -- ...
* -1|______ -
* interpolated to
* +1| - -
* 0| _--- -- ...
* -1|__- -
* ↑-- Rough, white noise
* -1 means -MAX_HILL_HEIGHT
*/
for (i in 0..OCEAN_WIDTH - 1) {
if (worldOceanPosition == TYPE_OCEAN_LEFT) {
noiseArrayLocal[i] = Math.round(
interpolateCosine(
i.toFloat() / OCEAN_WIDTH, (-MAX_OCEAN_DEPTH).toFloat(), oceanLeftP1.toFloat()))
} else if (worldOceanPosition == TYPE_OCEAN_RIGHT) {
noiseArrayLocal[noiseArrayLocal.size - OCEAN_WIDTH + i] = Math.round(
interpolateCosine(
i.toFloat() / OCEAN_WIDTH, oceanRightP1.toFloat(), (-MAX_OCEAN_DEPTH).toFloat()))
} else {
throw RuntimeException("Ocean position were not set correctly.")
}
}
return noiseArrayLocal
}
/**
* http://accidentalnoise.sourceforge.net/minecraftworlds.html
*/
private fun raise3(): Array<BitSet> {
val noiseMap = Array(HEIGHT, { BitSet(WIDTH) })
// Height = Terrain undulation times 2.
val SCALE_X: Double = (TERRAIN_UNDULATION * 0.5).toDouble()
val SCALE_Y: Double = (TERRAIN_UNDULATION * 0.25).toDouble()
val ground_gradient = ModuleGradient()
ground_gradient.setGradient(0.0, 0.0, 0.0, 1.0)
/* Lowlands */
val lowland_shape_fractal = ModuleFractal()
lowland_shape_fractal.setType(ModuleFractal.FractalType.FBM)
lowland_shape_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
lowland_shape_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
lowland_shape_fractal.setNumOctaves(4)
lowland_shape_fractal.setFrequency(0.6)
lowland_shape_fractal.seed = SEED xor random.nextLong()
//println(lowland_shape_fractal.seed)
val lowland_autocorrect = ModuleAutoCorrect()
lowland_autocorrect.setRange(0.0, 1.0)
lowland_autocorrect.setSource(lowland_shape_fractal)
val lowland_scale = ModuleScaleOffset()
lowland_scale.setSource(lowland_autocorrect)
lowland_scale.setScale(0.8)
lowland_scale.setOffset(-2.75)
val lowland_y_scale = ModuleScaleDomain()
lowland_y_scale.setSource(lowland_scale)
lowland_y_scale.setScaleY(0.0)
val lowland_terrain = ModuleTranslateDomain()
lowland_terrain.setSource(ground_gradient)
lowland_terrain.setAxisYSource(lowland_y_scale)
/* highlands */
val highland_shape_fractal = ModuleFractal()
highland_shape_fractal.setType(ModuleFractal.FractalType.RIDGEMULTI)
highland_shape_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
highland_shape_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
highland_shape_fractal.setNumOctaves(4)
highland_shape_fractal.setFrequency(0.5) // horizontal size. Higher == narrower
highland_shape_fractal.seed = SEED xor random.nextLong()
//println(highland_shape_fractal.seed)
val highland_autocorrect = ModuleAutoCorrect()
highland_autocorrect.setSource(highland_shape_fractal)
highland_autocorrect.setRange(0.0, 1.0)
val highland_scale = ModuleScaleOffset()
highland_scale.setSource(highland_autocorrect)
highland_scale.setScale(1.4) // vertical size. Higher == taller
highland_scale.setOffset(-2.25)
val highland_y_scale = ModuleScaleDomain()
highland_y_scale.setSource(highland_scale)
highland_y_scale.setScaleY(0.0)
val highland_terrain = ModuleTranslateDomain()
highland_terrain.setSource(ground_gradient)
highland_terrain.setAxisYSource(highland_y_scale)
/* mountains */
val mountain_shape_fractal = ModuleFractal()
mountain_shape_fractal.setType(ModuleFractal.FractalType.BILLOW)
mountain_shape_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
mountain_shape_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
mountain_shape_fractal.setNumOctaves(6)
mountain_shape_fractal.setFrequency(0.55)
mountain_shape_fractal.seed = SEED xor random.nextLong()
//println(mountain_shape_fractal.seed)
val mountain_autocorrect = ModuleAutoCorrect()
mountain_autocorrect.setSource(mountain_shape_fractal)
mountain_autocorrect.setRange(0.0, 1.0)
val mountain_scale = ModuleScaleOffset()
mountain_scale.setSource(mountain_autocorrect)
mountain_scale.setScale(1.66)
mountain_scale.setOffset(-1.25)
val mountain_y_scale = ModuleScaleDomain()
mountain_y_scale.setSource(mountain_scale)
mountain_y_scale.setScaleY(0.1)
val mountain_terrain = ModuleTranslateDomain()
mountain_terrain.setSource(ground_gradient)
mountain_terrain.setAxisYSource(mountain_y_scale)
/* selection */
val terrain_type_fractal = ModuleFractal()
terrain_type_fractal.setType(ModuleFractal.FractalType.MULTI)
terrain_type_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
terrain_type_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
terrain_type_fractal.setNumOctaves(5)
terrain_type_fractal.setFrequency(0.4) // <= 0.33
terrain_type_fractal.seed = SEED xor random.nextLong()
//println(terrain_type_fractal.seed)
val terrain_autocorrect = ModuleAutoCorrect()
terrain_autocorrect.setSource(terrain_type_fractal)
terrain_autocorrect.setRange(0.0, 1.0)
val terrain_type_scale = ModuleScaleDomain()
terrain_type_scale.setScaleY(0.33)
terrain_type_scale.setSource(terrain_autocorrect)
val terrain_type_cache = ModuleCache()
terrain_type_cache.setSource(terrain_type_scale)
val highland_mountain_select = ModuleSelect()
highland_mountain_select.setLowSource(highland_terrain)
highland_mountain_select.setHighSource(mountain_terrain)
highland_mountain_select.setControlSource(terrain_type_cache)
highland_mountain_select.setThreshold(0.55)
highland_mountain_select.setFalloff(0.15)
val highland_lowland_select = ModuleSelect()
highland_lowland_select.setLowSource(lowland_terrain)
highland_lowland_select.setHighSource(highland_mountain_select)
highland_lowland_select.setControlSource(terrain_type_cache)
highland_lowland_select.setThreshold(0.25)
highland_lowland_select.setFalloff(0.15)
val ground_select = ModuleSelect()
ground_select.setLowSource(0.0)
ground_select.setHighSource(1.0)
ground_select.setThreshold(0.5)
ground_select.setControlSource(highland_lowland_select)
val joise = Joise(ground_select)
// fill the area as Joise map
println("[mapgenerator] Raising and eroding terrain...")
for (y in 0..(TERRAIN_UNDULATION - 1)) {
for (x in 0..WIDTH) {
val map: Boolean = (
joise.get(
x / SCALE_X,
y / SCALE_Y
) == 1.0)
noiseMap[y + TERRAIN_AVERAGE_HEIGHT - (TERRAIN_UNDULATION / 2)].set(x, map)
}
}
// fill the area bottom of the above map as 'filled'
for (y in TERRAIN_AVERAGE_HEIGHT + (TERRAIN_UNDULATION / 2)..HEIGHT - 1) {
for (x in 0..WIDTH) {
noiseMap[y].set(x, true)
}
}
return noiseMap
}
/**
* | ----
* | ---
* | ---
* | --
* | -
* | --
* | ---
* | ---
* - ----------------------------
* @param func_x
* *
* @return
*/
private fun getGlacierMountedAmplitude(func_x: Int): Float {
if (func_x > GLACIER_MOUNTAIN_WIDTH) {
return 0f
} else {
val func_y = GLACIER_MOUNTAIN_HEIGHT / 2f * Math.cos((10 * func_x / (FastMath.PI * GLACIER_MOUNTAIN_WIDTH)).toDouble()).toFloat() + GLACIER_MOUNTAIN_HEIGHT / 2
return func_y
}
}
private fun placeGlacierMount(heightMap: IntArray) {
println("[mapgenerator] Putting glacier...")
// raise
for (i in heightMap.indices) {
if (worldOceanPosition == TYPE_OCEAN_RIGHT) {
heightMap[i] += Math.round(getGlacierMountedAmplitude(i))
} else {
heightMap[i] += Math.round(getGlacierMountedAmplitude(heightMap.size - i - 1))
}
}
}
/**
* Cosine interpolation between point a and b.
* @param x [0.0, 1.0] relative position between a and b
* *
* @param a leftmost point
* *
* @param b rightmost point
* *
* @return
*/
private fun interpolateCosine(x: Float, a: Float, b: Float): Float {
val ft = x * FastMath.PI
val f = (1 - FastMath.cos(ft)) * 0.5f
return a * (1 - f) + b * f
}
private fun heightMapToObjectMap(fs: IntArray) {
println("[mapgenerator] Shaping world as processed...")
// iterate for heightmap
for (x in 0..WIDTH - 1) {
val medianPosition = TERRAIN_AVERAGE_HEIGHT
val pillarOffset = medianPosition - fs[x]
// for pillar length
for (i in 0..HEIGHT - pillarOffset - 1) {
if (i < DIRT_LAYER_DEPTH) {
map.setTileTerrain(x, i + pillarOffset, TileNameCode.DIRT)
map.setTileWall(x, i + pillarOffset, TileNameCode.DIRT)
} else {
map.setTileTerrain(x, i + pillarOffset, TileNameCode.STONE)
map.setTileWall(x, i + pillarOffset, TileNameCode.STONE)
}
}
}
}
private fun fillMapByNoiseMap() {
println("[mapgenerator] Shaping world...")
// generate dirt-stone transition line
// use catmull spline
val dirtStoneLine = IntArray(WIDTH)
val POINTS_GAP = 64 // power of two!
val splineControlPoints = Array((WIDTH / POINTS_GAP) + 1, { Pair(0, 0) })
// get spline points
for (x in 0..(WIDTH / POINTS_GAP)) {
for (y in 0..TERRAIN_AVERAGE_HEIGHT + TERRAIN_UNDULATION) {
splineControlPoints[x] = Pair(x * POINTS_GAP, y)
if (terrainMap[y].get(splineControlPoints[x].first)) break
}
// println("Spline[$x] x: ${splineControlPoints[x].first}, " +
// "y: ${splineControlPoints[x].second}")
}
// do interpolation
for (x in 0..dirtStoneLine.size - 1) {
val x_1 = x / POINTS_GAP
val splineX0 = splineControlPoints[ clamp(x_1 - 1, 0, dirtStoneLine.size / POINTS_GAP) ].first
val splineX1 = splineControlPoints[x_1].first
val splineX2 = splineControlPoints[ clamp(x_1 + 1, 0, dirtStoneLine.size / POINTS_GAP) ].first
val splineX3 = splineControlPoints[ clamp(x_1 + 2, 0, dirtStoneLine.size / POINTS_GAP) ].first
val splineP0 = splineControlPoints[ clamp(x_1 - 1, 0, dirtStoneLine.size / POINTS_GAP) ].second.toFloat()
val splineP1 = splineControlPoints[x_1].second.toFloat()
val splineP2 = splineControlPoints[ clamp(x_1 + 1, 0, dirtStoneLine.size / POINTS_GAP) ].second.toFloat()
val splineP3 = splineControlPoints[ clamp(x_1 + 2, 0, dirtStoneLine.size / POINTS_GAP) ].second.toFloat()
if (x in POINTS_GAP - 1..WIDTH - 2 * POINTS_GAP) {
dirtStoneLine[x] = Math.round(FastMath.interpolateCatmullRom(
(x - splineX1) / POINTS_GAP.toFloat(),
-0.3f,//0.01f,
splineP0,
splineP1,
splineP2,
splineP3
))
}
else {
dirtStoneLine[x] = Math.round(FastMath.interpolateCatmullRom(
(x - splineX1) / POINTS_GAP.toFloat(),
-0.3f,//0.01f,
splineP0,
splineP1,
splineP2,
splineP3
))
}
}
// scan vertically
for (x in 0..WIDTH - 1) {
for (y in 0..HEIGHT - 1) {
if (terrainMap[clamp(y + DIRT_LAYER_DEPTH, 0, HEIGHT - 1)].get(x)) {
// map.setTileTerrain(x, y, TileNameCode.DIRT)
// map.setTileWall(x, y, TileNameCode.DIRT)
val tile =
if (y < dirtStoneLine[x]) TileNameCode.DIRT
else TileNameCode.STONE
map.setTileTerrain(x, y, tile)
map.setTileWall(x, y, tile)
}
}
}
}
/* 2. Carve */
/**
* Carve (place specified block) by noisemap, inversed gradation filter applied.
* @param map noisemap
* *
* @param scarcity higher == rarer
* * 1.0 is a default value. This value works as a multiplier to the gradient filter.
* @param tile
* *
* @param message
*/
private fun carveByMap(noisemap: Any, scarcity: Float, tile: Int, message: String,
filter: NoiseFilter = NoiseFilterQuadratic,
filterStart: Float = NOISE_GRAD_START,
filterEnd: Float = NOISE_GRAD_END) {
println("[mapgenerator] " + message)
for (y in 0..HEIGHT - 1) {
for (x in 0..WIDTH - 1) {
val noise: Float = when (noisemap) {
is Joise ->
noisemap.get(
x.toDouble() / 48.0, // 48: Fixed value
y.toDouble() / 48.0
).toFloat()
is TaggedSimplexNoise -> noisemap.noiseModule.getNoise(
Math.round(x / noisemap.xStretch),
Math.round(y / noisemap.yStretch)
)
else -> throw(IllegalArgumentException("[mapgenerator] Unknown noise module type '${noisemap.javaClass.simpleName}': Only the 'Joise' or 'TaggedSimplexNoise' is valid."))
}
if (noise > filter.getGrad(y, filterStart, filterEnd) * scarcity) {
map.setTileTerrain(x, y, tile)
}
}
}
}
private fun fillByMap(noisemap: Any, scarcity: Float, replaceFrom: Int, replaceTo: Int, message: String,
filter: NoiseFilter = NoiseFilterQuadratic,
filterStart: Float = NOISE_GRAD_START,
filterEnd: Float = NOISE_GRAD_END) {
println("[mapgenerator] " + message)
for (y in 0..HEIGHT - 1) {
for (x in 0..WIDTH - 1) {
val noise: Float = when (noisemap) {
is Joise ->
noisemap.get(
x.toDouble() / 48.0, // 48: Fixed value
y.toDouble() / 48.0
).toFloat()
is TaggedSimplexNoise -> noisemap.noiseModule.getNoise(
Math.round(x / noisemap.xStretch),
Math.round(y / noisemap.yStretch)
)
else -> throw(IllegalArgumentException("[mapgenerator] Unknown noise module type '${noisemap.javaClass.simpleName}': Only the 'Joise' or 'TaggedSimplexNoise' is valid."))
}
if (noise > filter.getGrad(y, filterStart, filterEnd) * scarcity
&& map.getTileFromTerrain(x, y) == replaceFrom) {
map.setTileTerrain(x, y, replaceTo)
}
}
}
}
private fun fillByMap(noisemap: Any, scarcity: Float, replaceFrom: Int, tile: IntArray, message: String,
filter: NoiseFilter = NoiseFilterQuadratic,
filterStart: Float = NOISE_GRAD_START,
filterEnd: Float = NOISE_GRAD_END) {
println("[mapgenerator] " + message)
for (y in 0..HEIGHT - 1) {
for (x in 0..WIDTH - 1) {
val noise: Float = when (noisemap) {
is Joise ->
noisemap.get(
x.toDouble() / 48.0, // 48: Fixed value
y.toDouble() / 48.0
).toFloat()
is TaggedSimplexNoise -> noisemap.noiseModule.getNoise(
Math.round(x / noisemap.xStretch),
Math.round(y / noisemap.yStretch)
)
else -> throw(IllegalArgumentException("[mapgenerator] Unknown noise module type '${noisemap.javaClass.simpleName}': Only the 'Joise' or 'TaggedSimplexNoise' is valid."))
}
if (noise > filter.getGrad(y, filterStart, filterEnd) * scarcity && map.getTileFromTerrain(x, y) == replaceFrom) {
map.setTileTerrain(x, y, tile[random.nextInt(tile.size)])
}
}
}
}
private fun processNoiseLayers(noiseRecords: Array<TaggedJoise>) {
for (record in noiseRecords) {
println("[mapgenerator] ${record.message}...")
for (y in 0..HEIGHT - 1) {
for (x in 0..WIDTH - 1) {
val noise: Float = record.noiseModule.get(
x.toDouble() / 48.0, // 48: Fixed value
y.toDouble() / 48.0
).toFloat()
val fromTerr = record.replaceFromTerrain
val fromWall = record.replaceFromWall
val to: Int = when(record.replaceTo) {
is Int -> record.replaceTo as Int
is IntArray -> (record.replaceTo as IntArray)[random.nextInt((record.replaceTo as IntArray).size)]
else -> throw IllegalArgumentException("[mapgenerator] Unknown replaceTo tile type '${record.replaceTo.javaClass.canonicalName}': Only 'kotlin.Int' and 'kotlin.IntArray' is valid.")
}
if (to == TILE_MACRO_ALL) throw IllegalArgumentException("[mapgenerator] Invalid replaceTo: TILE_MACRO_ALL")
val threshold = record.filter.getGrad(y, record.filterArg1, record.filterArg2)
if (noise > threshold * record.scarcity) {
if ((map.getTileFromTerrain(x, y) == fromTerr || fromTerr == TILE_MACRO_ALL)
&& (map.getTileFromWall(x, y) == fromWall || fromWall == TILE_MACRO_ALL)) {
map.setTileTerrain(x, y, to)
}
}
}
}
}
}
private fun generateFloatingIslands() {
println("[mapgenerator] Placing floating islands...")
val nIslandsMax = Math.round(map.width * 6f / 8192f)
val nIslandsMin = Math.max(2, Math.round(map.width * 4f / 8192f))
val nIslands = random.nextInt(nIslandsMax - nIslandsMin) + nIslandsMin
val prevIndex = -1
val tiles = intArrayOf(TileNameCode.AIR, TileNameCode.STONE, TileNameCode.DIRT, TileNameCode.GRASS)
for (i in 0..nIslands - 1) {
var currentIndex = random.nextInt(FloatingIslandsPreset.PRESETS)
while (currentIndex == prevIndex) {
currentIndex = random.nextInt(FloatingIslandsPreset.PRESETS)
}
val island = FloatingIslandsPreset.generatePreset(currentIndex, random)
val startingPosX = random.nextInt(map.width - 2048) + 1024
val startingPosY = minimumFloatingIsleHeight + random.nextInt(minimumFloatingIsleHeight)
for (j in island.indices) {
for (k in 0..island[0].size - 1) {
if (island[j][k] > 0) {
map.setTileTerrain(k + startingPosX, j + startingPosY, tiles[island[j][k]])
}
}
}
}
}
/* Flood */
private fun floodBottomLava() {
println("[mapgenerator] Flooding bottom lava...")
for (i in HEIGHT * 14 / 15..HEIGHT - 1) {
for (j in 0..WIDTH - 1) {
if (map.terrainArray[i][j].toInt() == 0) {
map.setTileTerrain(j, i, TileNameCode.LAVA)
}
}
}
}
/* Plant */
private fun plantGrass() {
println("[mapgenerator] Planting grass...")
/* TODO composing dirt and stone
* over certain level, use background dirt with stone 'peckles'
* beetween levels, use background dirt with larger and denser stone peckles.
* under another certain level, use background stone with dirt peckles.
*/
for (y in TERRAIN_AVERAGE_HEIGHT - TERRAIN_UNDULATION..TERRAIN_AVERAGE_HEIGHT + TERRAIN_UNDULATION - 1) {
for (x in 0..map.width - 1) {
val thisTile = map.getTileFromTerrain(x, y)
for (i in 0..8) {
var nearbyWallTile: Int?
nearbyWallTile = map.getTileFromWall(x + i % 3 - 1, y + i / 3 - 1)
if (nearbyWallTile == null) break;
if (i != 4 && thisTile == TileNameCode.DIRT && nearbyWallTile == TileNameCode.AIR) {
map.setTileTerrain(x, y, TileNameCode.GRASS)
break
}
}
}
}
}
private fun isGrassOrDirt(x: Int, y: Int): Boolean {
return map.getTileFromTerrain(x, y) == TileNameCode.GRASS || map.getTileFromTerrain(x, y) == TileNameCode.DIRT
}
private fun replaceIfTerrain(ifTileRaw: Int, x: Int, y: Int, replaceTileRaw: Int) {
if (map.getTileFromTerrain(x, y) == ifTileRaw) {
map.setTileTerrain(x, y, replaceTileRaw)
}
}
private fun replaceIfWall(ifTileRaw: Int, x: Int, y: Int, replaceTileRaw: Int) {
if (map.getTileFromWall(x, y) == ifTileRaw) {
map.setTileWall(x, y, replaceTileRaw)
}
}
/* Post-process */
private fun fillOcean() {
val thisSandList = intArrayOf(
TileNameCode.SAND, TileNameCode.SAND, TileNameCode.SAND, TileNameCode.SAND,
TileNameCode.SAND_WHITE, TileNameCode.SAND_WHITE, TileNameCode.SAND_WHITE,
TileNameCode.SAND_BLACK, TileNameCode.SAND_BLACK, TileNameCode.SAND_GREEN
)
val thisRand = HQRNG(SEED xor random.nextLong())
val thisSand = thisSandList[thisRand.nextInt(thisSandList.size)]
// val thisSand = TileNameCode.SAND_GREEN
val thisSandStr = if (thisSand == TileNameCode.SAND_BLACK)
"black"
else if (thisSand == TileNameCode.SAND_GREEN)
"green"
else if (thisSand == TileNameCode.SAND)
"yellow"
else
"white"
println("[mapgenerator] Beach sand type: $thisSandStr")
var ix = 0
while (ix < OCEAN_WIDTH * 1.5) {
//flooding
if (ix < OCEAN_WIDTH) {
if (worldOceanPosition == TYPE_OCEAN_LEFT) {
for (y in getTerrainHeightFromHeightMap(OCEAN_WIDTH)..getTerrainHeightFromHeightMap(ix) - 1) {
map.setTileTerrain(ix, y, TileNameCode.WATER)
}
} else if (worldOceanPosition == TYPE_OCEAN_RIGHT) {
for (y in getTerrainHeightFromHeightMap(map.width - 1 - OCEAN_WIDTH)..getTerrainHeightFromHeightMap(map.width - 1 - ix) - 1) {
map.setTileTerrain(map.width - 1 - ix, y, TileNameCode.WATER)
}
}
}
//sand
// linearly increase thickness of the sand sheet
for (iy in 0..40 - ix * 40 / (OCEAN_WIDTH + SHORE_WIDTH) - 1) {
if (worldOceanPosition == TYPE_OCEAN_LEFT) {
val terrainPoint = getTerrainHeightFromHeightMap(ix)
map.setTileTerrain(ix, terrainPoint + iy, thisSand)
// clear grass and make the sheet thicker
map.setTileTerrain(ix, terrainPoint + iy - 1, thisSand)
} else if (worldOceanPosition == TYPE_OCEAN_RIGHT) {
val terrainPoint = getTerrainHeightFromHeightMap(map.width - 1 - ix)
map.setTileTerrain(map.width - 1 - ix, terrainPoint + iy, thisSand)
// clear grass and make the sheet thicker
map.setTileTerrain(map.width - 1 - ix, terrainPoint + iy - 1, thisSand)
}
}
ix++
}
}
private fun freeze() {
for (y in 0..map.height - 1 - 1) {
for (x in 0..getFrozenAreaWidth(y) - 1) {
if (worldOceanPosition == TYPE_OCEAN_RIGHT) {
replaceIfTerrain(TileNameCode.DIRT, x, y, TileNameCode.SNOW)
replaceIfTerrain(TileNameCode.STONE, x, y, TileNameCode.ICE_NATURAL)
replaceIfWall(TileNameCode.DIRT, x, y, TileNameCode.SNOW)
replaceIfWall(TileNameCode.STONE, x, y, TileNameCode.ICE_NATURAL)
} else {
replaceIfTerrain(TileNameCode.DIRT, map.width - 1 - x, y, TileNameCode.SNOW)
replaceIfTerrain(TileNameCode.STONE, map.width - 1 - x, y, TileNameCode.ICE_NATURAL)
replaceIfWall(TileNameCode.DIRT, map.width - 1 - x, y, TileNameCode.SNOW)
replaceIfWall(TileNameCode.STONE, map.width - 1 - x, y, TileNameCode.ICE_NATURAL)
}
}
}
}
/**
* @return width of the frozen area for mapgenerator.freeze
*/
private fun getFrozenAreaWidth(y: Int): Int {
val randDeviation = 7
// narrower that the actual width
val width = Math.round(GLACIER_MOUNTAIN_WIDTH * 0.625f)
val height: Int
if (worldOceanPosition == TYPE_OCEAN_RIGHT) {
height = getTerrainHeightFromHeightMap(width)
} else {
height = getTerrainHeightFromHeightMap(map.width - 1 - width)
}
val k = width / FastMath.sqrt(height.toFloat())
if (y < height) {
// ground
return width
} else {
// underground
return Math.round(
k * FastMath.sqrt(y.toFloat()) + (random.nextInt(3) - 1))
}
}
/**
* @param x position of heightmap
* *
* @return
*/
private fun getTerrainHeightFromHeightMap(x: Int): Int {
TODO()
}
/* Utility */
private fun clampN(clampNumber: Int, num: Int): Int {
return FastMath.floor((num / clampNumber).toFloat()) * clampNumber
}
private fun outOfBound(w: Int, h: Int, x: Int, y: Int): Boolean {
return !(x > 0 && y > 0 && x < w && y < h)
}
private fun getDistance(x1: Float, y1: Float, x2: Float, y2: Float): Float {
return FastMath.sqrt(FastMath.pow(x1 - x2, 2f) + FastMath.pow(y2 - y1, 2f))
}
private fun circularDig(i: Int, j: Int, brushSize: Int, fillFrom: Int, fill: Int) {
val halfBrushSize = brushSize * 0.5f
for (pointerY in 0..brushSize - 1) {
for (pointerX in 0..brushSize - 1) {
if (getDistance(j.toFloat(), i.toFloat(), j + pointerX - halfBrushSize, i + pointerY - halfBrushSize) <= FastMath.floor((brushSize / 2).toFloat()) - 1) {
if (Math.round(j + pointerX - halfBrushSize) > brushSize
&& Math.round(j + pointerX - halfBrushSize) < WIDTH - brushSize
&& Math.round(i + pointerY - halfBrushSize) > brushSize
&& Math.round(i + pointerY - halfBrushSize) < HEIGHT - brushSize) {
if (map.terrainArray[Math.round(i + pointerY - halfBrushSize)][Math.round(j + pointerX - halfBrushSize)] == fillFrom.toByte()) {
map.terrainArray[Math.round(i + pointerY - halfBrushSize)][Math.round(j + pointerX - halfBrushSize)] = fill.toByte()
}
}
}
}
}
}
private fun clamp(x: Int, min: Int, max: Int): Int = if (x < min) min else if (x > max) max else x
data class TaggedSimplexNoise(var noiseModule: SimplexNoise, var xStretch: Float, var yStretch: Float)
data class TaggedJoise(var message: String,
var noiseModule: Joise, var scarcity: Float,
var replaceFromTerrain: Int, var replaceFromWall: Int,
var replaceTo: Any,
var filter: NoiseFilter = NoiseFilterQuadratic,
var filterArg1: Float = NOISE_GRAD_START,
var filterArg2: Float = NOISE_GRAD_END)
}

View File

@@ -0,0 +1,8 @@
package net.torvald.terrarum.mapgenerator
/**
* Created by minjaesong on 16-03-31.
*/
interface NoiseFilter {
fun getGrad(func_argX: Int, start: Float, end: Float): Float
}

View File

@@ -0,0 +1,46 @@
package net.torvald.terrarum.mapgenerator
import com.jme3.math.FastMath
/**
* Double Quadratic polynomial
* (16/9) * (start-end)/height^2 * (x-height)^2 + end
* 16/9: terrain is formed from 1/4 of height.
* 1 - (1/4) = 3/4, reverse it and square it.
* That makes 16/9.
* Shape:
* cavity -
* small
* -
* -
* --
* ----
* cavity --------
* large ----------------
* @param func_argX
* *
* @param start
* *
* @param end
* *
* @return
* Created by minjaesong on 16-03-31.
*/
object NoiseFilterCubic : NoiseFilter {
override fun getGrad(func_argX: Int, start: Float, end: Float): Float {
val graph_gradient = -FastMath.pow(FastMath.pow((1 - MapGenerator.TERRAIN_AVERAGE_HEIGHT).toFloat(), 3f), -1f) * // 1/4 -> 3/4 -> 9/16 -> 16/9
(start - end) / FastMath.pow(MapGenerator.HEIGHT.toFloat(), 3f) *
FastMath.pow((func_argX - MapGenerator.HEIGHT).toFloat(), 3f) + end
if (func_argX < MapGenerator.TERRAIN_AVERAGE_HEIGHT) {
return start
} else if (func_argX >= MapGenerator.HEIGHT) {
return end
} else {
return graph_gradient
}
}
}

View File

@@ -0,0 +1,46 @@
package net.torvald.terrarum.mapgenerator
import com.jme3.math.FastMath
/**
* Quadratic polynomial
* -(16/9) * (start-end)/height^2 * (x - 0.25 * height)^2 + start
* 16/9: terrain is formed from 1/4 of height.
* 1 - (1/4) = 3/4, reverse it and square it.
* That makes 16/9.
* Shape:
* cavity _
* small
* _
* _
* __
* ____
* cavity ________
* large ________________
* @param func_argX
* *
* @param start
* *
* @param end
* *
* @return
* Created by minjaesong on 16-03-31.
*/
object NoiseFilterMinusQuadratic : NoiseFilter {
override fun getGrad(func_argX: Int, start: Float, end: Float): Float {
val graph_gradient = -FastMath.pow(FastMath.sqr((1 - MapGenerator.TERRAIN_AVERAGE_HEIGHT).toFloat()), -1f) * // 1/4 -> 3/4 -> 9/16 -> 16/9
(start - end) / FastMath.sqr(MapGenerator.HEIGHT.toFloat()) *
FastMath.sqr((func_argX - MapGenerator.TERRAIN_AVERAGE_HEIGHT).toFloat()) + start
if (func_argX < MapGenerator.TERRAIN_AVERAGE_HEIGHT) {
return start
} else if (func_argX >= MapGenerator.HEIGHT) {
return end
} else {
return graph_gradient
}
}
}

View File

@@ -0,0 +1,47 @@
package net.torvald.terrarum.mapgenerator
import com.jme3.math.FastMath
/**
* Quadratic polynomial
* (16/9) * (start-end)/height^2 * (x-height)^2 + end
* 16/9: terrain is formed from 1/4 of height.
* 1 - (1/4) = 3/4, reverse it and square it.
* That makes 16/9.
* Shape:
* cavity -
* small
* -
* -
* --
* ----
* cavity --------
* large ----------------
* @param func_argX
* *
* @param start
* *
* @param end
* *
* @return
*
* Created by minjaesong on 16-03-31.
*/
object NoiseFilterQuadratic : NoiseFilter {
override fun getGrad(func_argX: Int, start: Float, end: Float): Float {
val graph_gradient = FastMath.pow(FastMath.sqr((1 - MapGenerator.TERRAIN_AVERAGE_HEIGHT).toFloat()), -1f) * // 1/4 -> 3/4 -> 9/16 -> 16/9
(start - end) / FastMath.sqr(MapGenerator.HEIGHT.toFloat()) *
FastMath.sqr((func_argX - MapGenerator.HEIGHT).toFloat()) + end
if (func_argX < MapGenerator.TERRAIN_AVERAGE_HEIGHT) {
return start
} else if (func_argX >= MapGenerator.HEIGHT) {
return end
} else {
return graph_gradient
}
}
}

View File

@@ -0,0 +1,20 @@
package net.torvald.terrarum.mapgenerator
import com.jme3.math.FastMath
/**
* Created by minjaesong on 16-03-31.
*/
object NoiseFilterSqrt : NoiseFilter {
override fun getGrad(func_argX: Int, start: Float, end: Float): Float {
val graph_gradient = (end - start) / FastMath.sqrt((MapGenerator.HEIGHT - MapGenerator.TERRAIN_AVERAGE_HEIGHT).toFloat()) * FastMath.sqrt((func_argX - MapGenerator.TERRAIN_AVERAGE_HEIGHT).toFloat()) + start
if (func_argX < MapGenerator.TERRAIN_AVERAGE_HEIGHT) {
return start
} else if (func_argX >= MapGenerator.HEIGHT) {
return end
} else {
return graph_gradient
}
}
}

View File

@@ -0,0 +1,10 @@
package net.torvald.terrarum.mapgenerator
/**
* Created by minjaesong on 16-03-31.
*/
object NoiseFilterUniform : NoiseFilter {
override fun getGrad(func_argX: Int, start: Float, end: Float): Float {
return 1f
}
}

Some files were not shown because too many files have changed in this diff Show More