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,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
/**
* <ReferenceID, Amounts>
*/
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 {
}