mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 19:44:05 +09:00
still wip modularisation, game somehow boots
This commit is contained in:
111
src/net/torvald/terrarum/modulebasegame/gameactors/AVKey.kt
Normal file
111
src/net/torvald/terrarum/modulebasegame/gameactors/AVKey.kt
Normal file
@@ -0,0 +1,111 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
/**
|
||||
* See [res/raw/Creature_raw_doc.md] for information about raw.
|
||||
*
|
||||
* Created by minjaesong on 2016-04-02.
|
||||
*/
|
||||
object AVKey {
|
||||
const val BUFF = "buff"
|
||||
|
||||
/** pixels per frame
|
||||
* walking/running speed
|
||||
*/
|
||||
const val SPEED = "speed"
|
||||
const val SPEEDBUFF = "$SPEED$BUFF"
|
||||
/** pixels per frame squared
|
||||
* acceleration of the movement (e.g. running, flying, driving, etc.)
|
||||
*/
|
||||
const val ACCEL = "accel"
|
||||
const val ACCELBUFF = "$ACCEL$BUFF"
|
||||
/** internal value used by ActorHumanoid to implement friction in walkfunction */
|
||||
const val SCALE = "scale"
|
||||
const val SCALEBUFF = "$SCALE$BUFF" // aka PHYSIQUE
|
||||
/** pixels */
|
||||
const val BASEHEIGHT = "baseheight"
|
||||
/** kilogrammes */
|
||||
const val BASEMASS = "basemass"
|
||||
/** pixels per frame */
|
||||
const val JUMPPOWER = "jumppower"
|
||||
const val JUMPPOWERBUFF = "$JUMPPOWER$BUFF"
|
||||
|
||||
/** Int
|
||||
* "Default" value of 1 000
|
||||
*/
|
||||
const val STRENGTH = "strength"
|
||||
const val STRENGTHBUFF = "$STRENGTH$BUFF"
|
||||
const val ENCUMBRANCE = "encumbrance"
|
||||
/** 30-bit RGB (Int)
|
||||
* 0000 0010000000 0010000000 0010000000
|
||||
* ^ Red ^ Green ^ Blue
|
||||
*/
|
||||
const val LUMR = "luminosityred"
|
||||
const val LUMG = "luminositygreen"
|
||||
const val LUMB = "luminosityblue"
|
||||
const val LUMA = "luminosityuv"
|
||||
const val DRAGCOEFF = "dragcoeff"
|
||||
const val FALLDAMPENMULT = "falldampenmult"
|
||||
|
||||
/** String
|
||||
* e.g. Jarppi
|
||||
*/
|
||||
const val NAME = "name"
|
||||
|
||||
/** String
|
||||
* e.g. Duudsoni
|
||||
*/
|
||||
const val RACENAME = "racename"
|
||||
/** String
|
||||
* e.g. Duudsonit
|
||||
*/
|
||||
const val RACENAMEPLURAL = "racenameplural"
|
||||
/** killogrammes
|
||||
* will affect attack strength, speed and inventory label
|
||||
* (see "Attack momentum calculator.numbers")
|
||||
* e.g. Hatchet (tiny)
|
||||
*/
|
||||
const val TOOLSIZE = "toolsize"
|
||||
/** Boolean
|
||||
* whether the player can talk with it
|
||||
*/
|
||||
const val INTELLIGENT = "intelligent"
|
||||
|
||||
/** (unit TBA)
|
||||
* base defence point of the species
|
||||
*/
|
||||
const val BASEDEFENCE = "basedefence"
|
||||
/** (unit TBA)
|
||||
* current defence point of worn armour(s)
|
||||
*/
|
||||
const val ARMOURDEFENCE = "armourdefence"
|
||||
const val ARMOURDEFENCEBUFF = "$ARMOURDEFENCE$BUFF"
|
||||
|
||||
const val MAGICREGENRATE = "magicregenrate"
|
||||
const val MAGICREGENRATEBUFF = "$MAGICREGENRATE$BUFF"
|
||||
|
||||
/** Double
|
||||
*
|
||||
*/
|
||||
const val ACTION_INTERVAL = "actioninterval"
|
||||
|
||||
|
||||
/** String
|
||||
* UUID for certain fixtures
|
||||
*/
|
||||
const val UUID = "uuid"
|
||||
|
||||
|
||||
|
||||
const val __PLAYER_QUICKSLOTSEL = "__quickslotselection"
|
||||
|
||||
/** Double
|
||||
* When using tool/arm/etc. how long action button is held, in milliseconds (Int)
|
||||
* Or for NPCs, how long it has been waiting for next move
|
||||
*/
|
||||
const val __ACTION_TIMER = "__actiontimer"
|
||||
|
||||
|
||||
|
||||
const val HEALTH = "health"
|
||||
const val MAGIC = "magic"
|
||||
}
|
||||
@@ -0,0 +1,648 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.jme3.math.FastMath
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.bipolarClamp
|
||||
import net.torvald.terrarum.gameactors.Controllable
|
||||
import net.torvald.terrarum.gameactors.Factionable
|
||||
import net.torvald.terrarum.gameactors.Hitbox
|
||||
import net.torvald.terrarum.gameactors.Luminous
|
||||
import net.torvald.terrarum.gameactors.faction.Faction
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.itemproperties.GameItem
|
||||
import net.torvald.terrarum.itemproperties.Material
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull
|
||||
import net.torvald.terrarum.worlddrawer.LightmapRenderer
|
||||
import org.dyn4j.geometry.Vector2
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Humanoid actor class to provide same controlling function (such as work, jump)
|
||||
* Also applies unreal air friction for movement control
|
||||
*
|
||||
* Created by minjaesong on 2016-10-24.
|
||||
*/
|
||||
open class ActorHumanoid(
|
||||
world: GameWorld,
|
||||
birth: GameDate,
|
||||
death: GameDate? = null,
|
||||
usePhysics: Boolean = true
|
||||
) : HistoricalFigure(world, birth, death, usePhysics = usePhysics), Controllable, Pocketed, Factionable, Luminous, LandHolder {
|
||||
|
||||
|
||||
|
||||
var vehicleRiding: Controllable? = null // usually player only
|
||||
|
||||
|
||||
|
||||
/** Must be set by PlayerFactory */
|
||||
override var inventory: ActorInventory = ActorInventory(this, 2000, ActorInventory.CAPACITY_MODE_WEIGHT) // default constructor
|
||||
|
||||
|
||||
/** Must be set by PlayerFactory */
|
||||
override var faction: HashSet<Faction> = HashSet()
|
||||
/**
|
||||
* Absolute tile index. index(x, y) = y * map.width + x
|
||||
* The arraylist will be saved in JSON format with GSON.
|
||||
*/
|
||||
override var houseDesignation: ArrayList<Long>? = ArrayList()
|
||||
|
||||
override fun addHouseTile(x: Int, y: Int) {
|
||||
if (houseDesignation != null) houseDesignation!!.add(LandUtil.getBlockAddr(world, x, y))
|
||||
}
|
||||
|
||||
override fun removeHouseTile(x: Int, y: Int) {
|
||||
if (houseDesignation != null) houseDesignation!!.remove(LandUtil.getBlockAddr(world, x, y))
|
||||
}
|
||||
|
||||
override fun clearHouseDesignation() {
|
||||
if (houseDesignation != null) houseDesignation!!.clear()
|
||||
}
|
||||
|
||||
override var color: Color
|
||||
get() = Color(
|
||||
(actorValue.getAsFloat(AVKey.LUMR) ?: 0f) / LightmapRenderer.MUL_FLOAT,
|
||||
(actorValue.getAsFloat(AVKey.LUMG) ?: 0f) / LightmapRenderer.MUL_FLOAT,
|
||||
(actorValue.getAsFloat(AVKey.LUMB) ?: 0f) / LightmapRenderer.MUL_FLOAT,
|
||||
(actorValue.getAsFloat(AVKey.LUMA) ?: 0f) / LightmapRenderer.MUL_FLOAT
|
||||
)
|
||||
set(value) {
|
||||
actorValue[AVKey.LUMR] = value.r * LightmapRenderer.MUL_FLOAT
|
||||
actorValue[AVKey.LUMG] = value.g * LightmapRenderer.MUL_FLOAT
|
||||
actorValue[AVKey.LUMB] = value.b * LightmapRenderer.MUL_FLOAT
|
||||
actorValue[AVKey.LUMA] = value.a * LightmapRenderer.MUL_FLOAT
|
||||
}
|
||||
|
||||
/**
|
||||
* Arguments:
|
||||
*
|
||||
* Hitbox(x-offset, y-offset, width, height)
|
||||
* (Use ArrayList for normal circumstances)
|
||||
*/
|
||||
override val lightBoxList: List<Hitbox>
|
||||
get() = arrayOf(Hitbox(2.0, 2.0, hitbox.width - 3, hitbox.height - 3)).toList() // things are asymmetric!!
|
||||
// use getter; dimension of the player may change by time.
|
||||
|
||||
@Transient val BASE_DENSITY = 980.0
|
||||
|
||||
companion object {
|
||||
//@Transient internal const val ACCEL_MULT_IN_FLIGHT: Double = 0.21
|
||||
@Transient internal const val WALK_ACCEL_BASE: Double = 0.67
|
||||
|
||||
@Transient const val BASE_HEIGHT = 40
|
||||
// 0.33333 miliseconds
|
||||
@Transient const val BASE_ACTION_INTERVAL = 1.0 / 3.0
|
||||
|
||||
@Transient const val SPRITE_ROW_IDLE = 0
|
||||
@Transient const val SPRITE_ROW_WALK = 1
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// MOVEMENT RELATED FUNCTIONS //
|
||||
////////////////////////////////
|
||||
|
||||
var axisX = 0f
|
||||
var axisY = 0f
|
||||
var axisRX = 0f
|
||||
var axisRY = 0f
|
||||
|
||||
/** empirical value. */
|
||||
@Transient private val JUMP_ACCELERATION_MOD = 51.0 / 10000.0 // (170 * (17/MAX_JUMP_LENGTH)^2) / 10000.0
|
||||
@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
|
||||
|
||||
/** how long the jump button has down, in frames */
|
||||
internal var jumpCounter = 0
|
||||
internal var jumpAcc = 0.0
|
||||
/** how long the walk button has down, in frames */
|
||||
internal var walkCounterX = 0
|
||||
internal var walkCounterY = 0
|
||||
@Transient private val MAX_JUMP_LENGTH = 25 // manages "heaviness" of the jump control. Higher = heavier
|
||||
|
||||
private var readonly_totalX = 0.0
|
||||
private var readonly_totalY = 0.0
|
||||
|
||||
internal var jumping = false
|
||||
internal var airJumpingAllowed = 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_KEYBOARD = -13372f // leetz
|
||||
@Transient private val GAMEPAD_JUMP = 7
|
||||
|
||||
protected var isUpDown = false
|
||||
protected var isDownDown = false
|
||||
protected var isLeftDown = false
|
||||
protected var isRightDown = false
|
||||
protected var isJumpDown = false
|
||||
protected inline val isGamer: Boolean
|
||||
get() = if (Terrarum.ingame == null) false else this == (Terrarum.ingame!! as Ingame).player
|
||||
|
||||
|
||||
private val nullItem = object : GameItem() {
|
||||
override var dynamicID: Int = 0
|
||||
override val originalID = dynamicID
|
||||
override val isUnique: Boolean = false
|
||||
override var baseMass: Double = 0.0
|
||||
override var baseToolSize: Double? = null
|
||||
override var inventoryCategory = "should_not_be_seen"
|
||||
override val originalName: String = actorValue.getAsString(AVKey.NAME) ?: "(no name)"
|
||||
override var stackable = false
|
||||
override val isDynamic = false
|
||||
override val material = Material(0,0,0,0,0,0,0,0,0,0.0)
|
||||
}
|
||||
|
||||
override fun update(delta: Float) {
|
||||
super.update(delta)
|
||||
|
||||
if (vehicleRiding is Player)
|
||||
throw Error("Attempted to 'ride' player object. ($vehicleRiding)")
|
||||
if (vehicleRiding != null && vehicleRiding == this)
|
||||
throw Error("Attempted to 'ride' itself. ($vehicleRiding)")
|
||||
|
||||
|
||||
|
||||
// don't put this into keyPressed; execution order is important!
|
||||
updateGamerControlBox()
|
||||
|
||||
processInput(delta)
|
||||
|
||||
updateSprite(delta)
|
||||
|
||||
if (noClip) {
|
||||
//grounded = true
|
||||
}
|
||||
|
||||
// reset control box of AI
|
||||
if (!isGamer) {
|
||||
isUpDown = false
|
||||
isDownDown = false
|
||||
isLeftDown = false
|
||||
isRightDown = false
|
||||
isJumpDown = false
|
||||
axisX = 0f
|
||||
axisY = 0f
|
||||
axisRX = 0f
|
||||
axisRY = 0f
|
||||
}
|
||||
|
||||
// update inventory items
|
||||
inventory.forEach {
|
||||
if (!inventory.itemEquipped.contains(it.item)) { // unequipped
|
||||
it.item.effectWhileInPocket(delta)
|
||||
}
|
||||
else { // equipped
|
||||
it.item.effectWhenEquipped(delta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateGamerControlBox() {
|
||||
if (isGamer) {
|
||||
isUpDown = Gdx.input.isKeyPressed(Terrarum.getConfigInt("keyup"))
|
||||
isLeftDown = Gdx.input.isKeyPressed(Terrarum.getConfigInt("keyleft"))
|
||||
isDownDown = Gdx.input.isKeyPressed(Terrarum.getConfigInt("keydown"))
|
||||
isRightDown = Gdx.input.isKeyPressed(Terrarum.getConfigInt("keyright"))
|
||||
isJumpDown = Gdx.input.isKeyPressed(Terrarum.getConfigInt("keyjump"))
|
||||
|
||||
if (Terrarum.controller != null) {
|
||||
axisX = Terrarum.controller!!.getAxisValue(Terrarum.getConfigInt("joypadlstickx"))
|
||||
axisY = Terrarum.controller!!.getAxisValue(Terrarum.getConfigInt("joypadlsticky"))
|
||||
axisRX = Terrarum.controller!!.getAxisValue(Terrarum.getConfigInt("joypadrstickx"))
|
||||
axisRY = Terrarum.controller!!.getAxisValue(Terrarum.getConfigInt("joypadrsticky"))
|
||||
|
||||
// deadzonning
|
||||
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
|
||||
|
||||
isJumpDown = Gdx.input.isKeyPressed(Terrarum.getConfigInt("keyjump")) ||
|
||||
Terrarum.controller!!.isButtonPressed(GAMEPAD_JUMP)
|
||||
}
|
||||
}
|
||||
else {
|
||||
isUpDown = axisY < 0f
|
||||
isDownDown = axisY > 0f
|
||||
isLeftDown = axisX < 0f
|
||||
isRightDown = axisX > 0f
|
||||
}
|
||||
}
|
||||
|
||||
private inline val hasController: Boolean
|
||||
get() = if (isGamer) Terrarum.controller != null
|
||||
else true
|
||||
|
||||
private fun processInput(delta: Float) {
|
||||
|
||||
/**
|
||||
* L-R stop
|
||||
*/
|
||||
if (hasController && !isWalkingH) {
|
||||
if (axisX == 0f) {
|
||||
walkHStop()
|
||||
}
|
||||
}
|
||||
// ↑F, ↑S
|
||||
if (isWalkingH && !isLeftDown && !isRightDown) {
|
||||
walkHStop()
|
||||
prevHMoveKey = KEY_NULL
|
||||
}
|
||||
/**
|
||||
* U-D stop
|
||||
*/
|
||||
if (hasController) {
|
||||
if (axisY == 0f) {
|
||||
walkVStop()
|
||||
}
|
||||
}
|
||||
// ↑E
|
||||
// ↑D
|
||||
if (isNoClip() && !isUpDown && !isDownDown) {
|
||||
walkVStop()
|
||||
prevVMoveKey = KEY_NULL
|
||||
}
|
||||
|
||||
/**
|
||||
* Left/Right movement
|
||||
*/
|
||||
|
||||
if (hasController) {
|
||||
if (axisX != 0f) {
|
||||
walkHorizontal(axisX < 0f, axisX.abs())
|
||||
}
|
||||
}
|
||||
// ↑F, ↓S
|
||||
if (isRightDown && !isLeftDown) {
|
||||
walkHorizontal(false, AXIS_KEYBOARD)
|
||||
prevHMoveKey = Terrarum.getConfigInt("keyright")
|
||||
} // ↓F, ↑S
|
||||
else if (isLeftDown && !isRightDown) {
|
||||
walkHorizontal(true, AXIS_KEYBOARD)
|
||||
prevHMoveKey = Terrarum.getConfigInt("keyleft")
|
||||
} // ↓F, ↓S
|
||||
/*else if (isLeftDown && isRightDown) {
|
||||
if (prevHMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_LEFT)) {
|
||||
walkHorizontal(false, AXIS_KEYBOARD)
|
||||
prevHMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_RIGHT)
|
||||
} else if (prevHMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_RIGHT)) {
|
||||
walkHorizontal(true, AXIS_KEYBOARD)
|
||||
prevHMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_LEFT)
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Up/Down movement
|
||||
*/
|
||||
if (noClip || COLLISION_TEST_MODE) {
|
||||
if (hasController) {
|
||||
if (axisY != 0f) {
|
||||
walkVertical(axisY < 0, axisY.abs())
|
||||
}
|
||||
}
|
||||
// ↑E, ↓D
|
||||
if (isDownDown && !isUpDown) {
|
||||
walkVertical(false, AXIS_KEYBOARD)
|
||||
prevVMoveKey = Terrarum.getConfigInt("keydown")
|
||||
} // ↓E, ↑D
|
||||
else if (isUpDown && !isDownDown) {
|
||||
walkVertical(true, AXIS_KEYBOARD)
|
||||
prevVMoveKey = Terrarum.getConfigInt("keyup")
|
||||
} // ↓E, ↓D
|
||||
/*else if (isUpDown && isDownDown) {
|
||||
if (prevVMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_UP)) {
|
||||
walkVertical(false, AXIS_KEYBOARD)
|
||||
prevVMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_DOWN)
|
||||
} else if (prevVMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_DOWN)) {
|
||||
walkVertical(true, AXIS_KEYBOARD)
|
||||
prevVMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_UP)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump control
|
||||
*/
|
||||
if (isJumpDown) {
|
||||
if (!noClip) {
|
||||
if (airJumpingAllowed ||
|
||||
(!airJumpingAllowed && walledBottom)) {
|
||||
jumping = true
|
||||
}
|
||||
jump()
|
||||
}
|
||||
else {
|
||||
walkVertical(true, AXIS_KEYBOARD)
|
||||
}
|
||||
}
|
||||
else {
|
||||
jumping = false
|
||||
jumpCounter = 0
|
||||
jumpAcc = 0.0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun keyDown(keycode: Int): Boolean {
|
||||
// quickslot (quickbar)
|
||||
val quickbarKeys = Terrarum.getConfigIntArray("keyquickbars")
|
||||
if (keycode in quickbarKeys) {
|
||||
actorValue[AVKey.__PLAYER_QUICKSLOTSEL] = quickbarKeys.indexOf(keycode)
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This code directly controls VELOCITY for walking, called walkX and walkY.
|
||||
*
|
||||
* In theory, we must add ACCELERATION to the velocity, but unfortunately it's arduous task
|
||||
* with this simulation code base.
|
||||
*
|
||||
* Reason: we have naïve friction code that is not adaptive at all and to add proper walking code to
|
||||
* this code base, ACCELERATION must be changed (in other words, we must deal with JERK) accordingly
|
||||
* to the FRICTION.
|
||||
*
|
||||
* So I'm adding walkX/Y and getting the ActorWithPhysics.setNewNextHitbox to use the velocity value of
|
||||
* walkX/Y + velocity, which is stored in variable moveDelta.
|
||||
*
|
||||
* Be warned.
|
||||
*
|
||||
* @param left (even if the game is joypad controlled, you must give valid value)
|
||||
* @param absAxisVal (set AXIS_KEYBOARD if keyboard controlled)
|
||||
* @author minjaesong
|
||||
*/
|
||||
private fun walkHorizontal(left: Boolean, absAxisVal: Float) {
|
||||
|
||||
|
||||
if (avAcceleration.isNaN()) {
|
||||
throw Error("avAccelation is NaN")
|
||||
}
|
||||
|
||||
|
||||
if (left && walledLeft || !left && walledRight) return
|
||||
|
||||
|
||||
readonly_totalX =
|
||||
if (absAxisVal == AXIS_KEYBOARD)
|
||||
avAcceleration * applyVelo(walkCounterX) * (if (left) -1f else 1f)
|
||||
else
|
||||
avAcceleration * applyVelo(walkCounterX) * (if (left) -1f else 1f) * absAxisVal
|
||||
|
||||
if (absAxisVal != AXIS_KEYBOARD)
|
||||
controllerMoveDelta?.x?.let { controllerMoveDelta!!.x = controllerMoveDelta!!.x.plus(readonly_totalX).bipolarClamp(avSpeedCap * absAxisVal) }
|
||||
else
|
||||
controllerMoveDelta?.x?.let { controllerMoveDelta!!.x = controllerMoveDelta!!.x.plus(readonly_totalX).bipolarClamp(avSpeedCap) }
|
||||
|
||||
if (walkCounterX < 1000000) {
|
||||
walkCounterX += 1
|
||||
}
|
||||
|
||||
isWalkingH = true
|
||||
|
||||
|
||||
|
||||
// Heading flag
|
||||
walkHeading = if (left) LEFT else RIGHT
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* @param up (even if the game is joypad controlled, you must give valid value)
|
||||
* *
|
||||
* @param absAxisVal (set AXIS_KEYBOARD if keyboard controlled)
|
||||
*/
|
||||
private fun walkVertical(up: Boolean, absAxisVal: Float) {
|
||||
if (up && walledTop || !up && walledBottom) return
|
||||
|
||||
|
||||
if (avAcceleration.isNaN()) {
|
||||
throw Error("avAccelation is NaN")
|
||||
}
|
||||
|
||||
|
||||
readonly_totalY =
|
||||
if (absAxisVal == AXIS_KEYBOARD)
|
||||
avAcceleration * applyVelo(walkCounterY) * (if (up) -1f else 1f)
|
||||
else
|
||||
avAcceleration * applyVelo(walkCounterY) * (if (up) -1f else 1f) * absAxisVal
|
||||
|
||||
if (absAxisVal != AXIS_KEYBOARD)
|
||||
controllerMoveDelta?.y?.let { controllerMoveDelta!!.y = controllerMoveDelta!!.y.plus(readonly_totalY).bipolarClamp(avSpeedCap * absAxisVal) }
|
||||
else
|
||||
controllerMoveDelta?.y?.let { controllerMoveDelta!!.y = controllerMoveDelta!!.y.plus(readonly_totalY).bipolarClamp(avSpeedCap) }
|
||||
|
||||
if (walkCounterY < 1000000) {
|
||||
walkCounterY += 1
|
||||
}
|
||||
|
||||
|
||||
isWalkingV = true
|
||||
}
|
||||
|
||||
private fun applyAccel(x: Int): Double {
|
||||
return if (x < WALK_FRAMES_TO_MAX_ACCEL)
|
||||
Math.sin(Math.PI * x / WALK_FRAMES_TO_MAX_ACCEL)
|
||||
else 0.0
|
||||
}
|
||||
|
||||
private fun applyVelo(x: Int): Double {
|
||||
return if (x < WALK_FRAMES_TO_MAX_ACCEL)
|
||||
0.5 - 0.5 * Math.cos(Math.PI * x / WALK_FRAMES_TO_MAX_ACCEL)
|
||||
else 1.0
|
||||
}
|
||||
|
||||
// stops; let the friction kick in by doing nothing to the velocity here
|
||||
private fun walkHStop() {
|
||||
walkCounterX = 0
|
||||
isWalkingH = false
|
||||
}
|
||||
|
||||
// stops; let the friction kick in by doing nothing to the velocity here
|
||||
private fun walkVStop() {
|
||||
walkCounterY = 0
|
||||
isWalkingV = false
|
||||
}
|
||||
|
||||
private fun getJumpAcc(pwr: Double, timedJumpCharge: Double): Double {
|
||||
return pwr * timedJumpCharge * JUMP_ACCELERATION_MOD * Math.sqrt(scale) // positive value
|
||||
}
|
||||
|
||||
private var oldMAX_JUMP_LENGTH = -1 // init
|
||||
private var oldJUMPPOWER = -1.0 // init
|
||||
private var oldJUMPPOWERBUFF = -1.0 // init
|
||||
private var oldScale = -1.0
|
||||
private var oldDragCoefficient = -1.0
|
||||
val jumpAirTime: Double = -1.0
|
||||
get() {
|
||||
// compare all the affecting variables
|
||||
if (oldMAX_JUMP_LENGTH == MAX_JUMP_LENGTH &&
|
||||
oldJUMPPOWER == actorValue.getAsDouble(AVKey.JUMPPOWER)!! &&
|
||||
oldJUMPPOWERBUFF == actorValue.getAsDouble(AVKey.JUMPPOWERBUFF) ?: 1.0 &&
|
||||
oldScale == scale &&
|
||||
oldDragCoefficient == dragCoefficient) {
|
||||
return field
|
||||
}
|
||||
// if variables are changed, get new value, store it and return it
|
||||
else {
|
||||
oldMAX_JUMP_LENGTH = MAX_JUMP_LENGTH
|
||||
oldJUMPPOWER = actorValue.getAsDouble(AVKey.JUMPPOWER)!!
|
||||
oldJUMPPOWERBUFF = actorValue.getAsDouble(AVKey.JUMPPOWERBUFF) ?: 1.0
|
||||
oldScale = scale
|
||||
oldDragCoefficient = dragCoefficient
|
||||
|
||||
|
||||
var frames = 0
|
||||
|
||||
var simYPos = 0.0
|
||||
var forceVec = Vector2(0.0, 0.0)
|
||||
var jmpCtr = 0
|
||||
while (true) {
|
||||
if (jmpCtr < MAX_JUMP_LENGTH) jmpCtr++
|
||||
|
||||
|
||||
val timedJumpCharge = jumpFunc(MAX_JUMP_LENGTH, jmpCtr)
|
||||
forceVec.y -= getJumpAcc(jumpPower, timedJumpCharge)
|
||||
forceVec.y += getDrag(forceVec).y
|
||||
|
||||
simYPos += forceVec.y // ignoring all the fluid drag OTHER THAN THE AIR
|
||||
|
||||
|
||||
if ((simYPos >= 0.0 && frames > 0) || frames >= 1000) break
|
||||
|
||||
|
||||
frames++
|
||||
}
|
||||
|
||||
|
||||
field = frames * (1.0 / Terrarum.TARGET_FPS)
|
||||
// fixme: looks good but return value is wrong -- 2.25 seconds? when I jump it barely goes past 1 sec
|
||||
|
||||
|
||||
return field
|
||||
}
|
||||
}
|
||||
|
||||
private val jumpPower: Double
|
||||
get() = actorValue.getAsDouble(AVKey.JUMPPOWER)!! * (actorValue.getAsDouble(AVKey.JUMPPOWERBUFF) ?: 1.0)
|
||||
|
||||
private fun jumpFunc(len: Int, counter: Int): Double {
|
||||
// linear time mode
|
||||
val init = (len + 1) / 2.0
|
||||
var timedJumpCharge = init - init / len * counter
|
||||
if (timedJumpCharge < 0) timedJumpCharge = 0.0
|
||||
return timedJumpCharge
|
||||
}
|
||||
|
||||
/**
|
||||
* See ./work_files/Jump power by pressing time.gcx
|
||||
*
|
||||
* TODO linear function (play Super Mario Bros. and you'll get what I'm talking about) -- SCRATCH THAT!
|
||||
*/
|
||||
private fun jump() {
|
||||
if (jumping) {// && jumpable) {
|
||||
// increment jump counter
|
||||
if (jumpCounter < MAX_JUMP_LENGTH) jumpCounter += 1
|
||||
|
||||
val timedJumpCharge = jumpFunc(MAX_JUMP_LENGTH, jumpCounter)
|
||||
|
||||
jumpAcc = getJumpAcc(jumpPower, timedJumpCharge)
|
||||
|
||||
controllerMoveDelta?.y?.let { controllerMoveDelta!!.y -= jumpAcc } // feed negative value to the vector
|
||||
// do not think of resetting this to zero when counter hit the ceiling; that's HOW NOT
|
||||
// newtonian physics work, stupid myself :(
|
||||
|
||||
}
|
||||
// not sure we need this...
|
||||
/*else if (!jumpable) {
|
||||
jumpable = true // this is kind of like "semaphore", we toggle it now
|
||||
grounded = false // just in case...
|
||||
}*/
|
||||
|
||||
// release "jump key" of AIs
|
||||
if (jumpCounter >= MAX_JUMP_LENGTH && !isGamer) {
|
||||
isJumpDown = false
|
||||
jumping = false
|
||||
jumpCounter = 0
|
||||
jumpAcc = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActorValueChange(key: String, value: Any?) {
|
||||
// quickslot implementation
|
||||
if (key == AVKey.__PLAYER_QUICKSLOTSEL && value != null) {
|
||||
// ONLY FOR HAND_GRIPs!!
|
||||
val quickBarItem = inventory.getQuickBar(actorValue.getAsInt(key)!!)?.item
|
||||
|
||||
if (quickBarItem != null && quickBarItem.equipPosition == GameItem.EquipPosition.HAND_GRIP) {
|
||||
equipItem(quickBarItem)
|
||||
}
|
||||
|
||||
// force update inventory UI
|
||||
try {
|
||||
((Terrarum.ingame!! as Ingame).uiInventoryPlayer as UIInventoryFull).rebuildList()
|
||||
}
|
||||
catch (LateInitMyArse: kotlin.UninitializedPropertyAccessException) { }
|
||||
}
|
||||
}
|
||||
|
||||
fun isNoClip(): Boolean {
|
||||
return noClip
|
||||
}
|
||||
|
||||
fun setNoClip(b: Boolean) {
|
||||
noClip = b
|
||||
|
||||
if (b) {
|
||||
externalForce.zero()
|
||||
controllerMoveDelta?.zero()
|
||||
}
|
||||
}
|
||||
|
||||
fun Float.abs() = FastMath.abs(this)
|
||||
|
||||
private fun updateSprite(delta: Float) {
|
||||
sprite?.update(delta)
|
||||
spriteGlow?.update(delta)
|
||||
|
||||
//println("$this\tsprite current frame: ${sprite!!.currentFrame}")
|
||||
|
||||
if (walledBottom) {
|
||||
// set anim row
|
||||
if (controllerMoveDelta?.x != 0.0) {
|
||||
sprite?.switchRow(SPRITE_ROW_WALK)
|
||||
spriteGlow?.switchRow(SPRITE_ROW_WALK)
|
||||
}
|
||||
|
||||
// flipping the sprite
|
||||
if (walkHeading == LEFT) {
|
||||
sprite?.flip(true, false)
|
||||
spriteGlow?.flip(true, false)
|
||||
}
|
||||
else {
|
||||
sprite?.flip(false, false)
|
||||
spriteGlow?.flip(false, false)
|
||||
}
|
||||
}
|
||||
else {
|
||||
sprite?.switchRow(SPRITE_ROW_IDLE)
|
||||
spriteGlow?.switchRow(SPRITE_ROW_IDLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
import net.torvald.terrarum.itemproperties.GameItem
|
||||
import net.torvald.terrarum.itemproperties.ItemCodex
|
||||
import net.torvald.terrarum.itemproperties.ItemCodex.ITEM_DYNAMIC
|
||||
import net.torvald.terrarum.itemproperties.ItemCodex.ITEM_WALLS
|
||||
import net.torvald.terrarum.itemproperties.ItemID
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.Lock
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-03-15.
|
||||
*/
|
||||
|
||||
class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode: Int) {
|
||||
|
||||
companion object {
|
||||
@Transient val CAPACITY_MODE_NO_ENCUMBER = 0
|
||||
@Transient val CAPACITY_MODE_COUNT = 1
|
||||
@Transient val CAPACITY_MODE_WEIGHT = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all equipped items (tools, armours, rings, necklaces, etc.)
|
||||
*/
|
||||
val itemEquipped = Array<GameItem?>(GameItem.EquipPosition.INDEX_MAX, { null })
|
||||
|
||||
/**
|
||||
* Sorted by referenceID.
|
||||
*/
|
||||
val itemList = ArrayList<InventoryPair>()
|
||||
val quickBar = Array<ItemID?>(10, { null }) // 0: Slot 1, 9: Slot 10
|
||||
|
||||
var currency = 0 // unified currency for whole civs; Dwarf Fortress approach seems too complicated
|
||||
|
||||
init {
|
||||
}
|
||||
|
||||
fun add(itemID: ItemID, count: Int = 1) = add(ItemCodex[itemID], count)
|
||||
fun add(item: GameItem, count: Int = 1) {
|
||||
|
||||
println("[ActorInventory] add $item, $count")
|
||||
|
||||
|
||||
// not wall-able walls
|
||||
if (item.inventoryCategory == GameItem.Category.WALL &&
|
||||
!BlockCodex[item.dynamicID - ITEM_WALLS.start].isWallable) {
|
||||
throw IllegalArgumentException("Wall ID ${item.dynamicID - ITEM_WALLS.start} is not wall-able.")
|
||||
}
|
||||
|
||||
|
||||
// other invalid values
|
||||
if (count == 0)
|
||||
throw IllegalArgumentException("Item count is zero.")
|
||||
if (count < 0)
|
||||
throw IllegalArgumentException("Item count is negative number. If you intended removing items, use remove()\n" +
|
||||
"These commands are NOT INTERCHANGEABLE; they handle things differently according to the context.")
|
||||
if (item.originalID == Player.PLAYER_REF_ID || item.originalID == 0x51621D) // do not delete this magic
|
||||
throw IllegalArgumentException("Attempted to put human player into the inventory.")
|
||||
if (((Terrarum.ingame as? Ingame)?.gameFullyLoaded ?: false) &&
|
||||
(item.originalID == (Terrarum.ingame as? Ingame)?.player?.referenceID))
|
||||
throw IllegalArgumentException("Attempted to put active player into the inventory.")
|
||||
if ((!item.stackable || item.dynamicID in ITEM_DYNAMIC) && count > 1)
|
||||
throw IllegalArgumentException("Attempting to adding stack of item but the item is not stackable; item: $item, count: $count")
|
||||
|
||||
|
||||
|
||||
// If we already have the item, increment the amount
|
||||
// If not, add item with specified amount
|
||||
val existingItem = getByDynamicID(item.dynamicID)
|
||||
|
||||
// if the item already exists
|
||||
if (existingItem != null) {
|
||||
// increment count
|
||||
existingItem.amount += count
|
||||
}
|
||||
// new item
|
||||
else {
|
||||
itemList.add(InventoryPair(item, count))
|
||||
}
|
||||
insertionSortLastElem(itemList)
|
||||
}
|
||||
|
||||
fun remove(itemID: ItemID, count: Int) = remove(ItemCodex[itemID], count)
|
||||
/** Will check existence of the item using its Dynamic ID; careful with command order!
|
||||
* e.g. re-assign after this operation */
|
||||
fun remove(item: GameItem, count: Int = 1) {
|
||||
|
||||
println("[ActorInventory] remove $item, $count")
|
||||
|
||||
if (count == 0)
|
||||
throw IllegalArgumentException("Item count is zero.")
|
||||
if (count < 0)
|
||||
throw IllegalArgumentException("Item count is negative number. If you intended adding items, use add()" +
|
||||
"These commands are NOT INTERCHANGEABLE; they handle things differently according to the context.")
|
||||
|
||||
|
||||
|
||||
val existingItem = getByDynamicID(item.dynamicID)
|
||||
if (existingItem != null) { // if the item already exists
|
||||
val newCount = existingItem.amount - count
|
||||
|
||||
if (newCount < 0) {
|
||||
throw Error("Tried to remove $count of $item, but the inventory only contains ${existingItem.amount} of them.")
|
||||
}
|
||||
else if (newCount > 0) {
|
||||
// decrement count
|
||||
existingItem.amount = newCount
|
||||
}
|
||||
else {
|
||||
// unequip, if applicable
|
||||
actor.unequipItem(existingItem.item)
|
||||
// depleted item; remove entry from inventory
|
||||
itemList.remove(existingItem)
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw Error("Tried to remove $item, but the inventory does not have it.")
|
||||
}
|
||||
}
|
||||
|
||||
fun setQuickBar(slot: Int, dynamicID: ItemID?) {
|
||||
quickBar[slot] = dynamicID
|
||||
}
|
||||
|
||||
fun getQuickBar(slot: Int): InventoryPair? = getByDynamicID(quickBar[slot])
|
||||
|
||||
/**
|
||||
* HashMap<GameItem, Amounts>
|
||||
*/
|
||||
inline fun forEach(consumer: (InventoryPair) -> Unit) = itemList.forEach(consumer)
|
||||
|
||||
/**
|
||||
* Get capacity of inventory
|
||||
* @return
|
||||
*/
|
||||
val capacity: Double
|
||||
get() = if (capacityMode == CAPACITY_MODE_NO_ENCUMBER)
|
||||
maxCapacity.toDouble()
|
||||
else if (capacityMode == CAPACITY_MODE_WEIGHT)
|
||||
getTotalWeight()
|
||||
else
|
||||
getTotalCount().toDouble()
|
||||
|
||||
fun getTotalWeight(): Double = itemList.map { it.item.mass * it.amount }.sum()
|
||||
|
||||
/**
|
||||
* Real amount
|
||||
*/
|
||||
fun getTotalCount(): Int = itemList.map { it.amount }.sum()
|
||||
|
||||
/**
|
||||
* Unique amount, multiple items are calculated as one
|
||||
*/
|
||||
fun getTotalUniqueCount(): Int = itemList.size
|
||||
|
||||
/**
|
||||
* Check whether the itemList contains too many items
|
||||
* @return
|
||||
*/
|
||||
val isEncumbered: Boolean
|
||||
get() = if (capacityMode == CAPACITY_MODE_NO_ENCUMBER)
|
||||
false
|
||||
else if (capacityMode == CAPACITY_MODE_WEIGHT)
|
||||
maxCapacity < capacity
|
||||
else
|
||||
false
|
||||
|
||||
|
||||
fun consumeItem(actor: Actor, item: GameItem) {
|
||||
if (item.stackable && !item.isDynamic) {
|
||||
remove(item, 1)
|
||||
}
|
||||
else {
|
||||
val newItem: GameItem
|
||||
|
||||
// unpack newly-made dynamic item (e.g. any weapon, floppy disk)
|
||||
if (item.isDynamic && item.originalID == item.dynamicID) {
|
||||
itemEquipped[item.equipPosition] = null
|
||||
remove(item, 1)
|
||||
|
||||
|
||||
newItem = item.clone()
|
||||
newItem.generateUniqueDynamicID(this)
|
||||
|
||||
newItem.stackable = false
|
||||
add(newItem)
|
||||
itemEquipped[newItem.equipPosition] = getByDynamicID(newItem.dynamicID)!!.item // will test if some sketchy code is written. Test fail: kotlinNullpointerException
|
||||
|
||||
// FIXME now damage meter (vital) is broken
|
||||
}
|
||||
else {
|
||||
newItem = item
|
||||
}
|
||||
|
||||
|
||||
|
||||
// calculate damage value
|
||||
val baseDamagePerSwing = if (actor is ActorHumanoid)
|
||||
actor.avStrength / 1000.0
|
||||
else
|
||||
1.0 // TODO variable: scale, strength
|
||||
val swingDmgToFrameDmg = Terrarum.deltaTime.toDouble() / actor.actorValue.getAsDouble(AVKey.ACTION_INTERVAL)!!
|
||||
|
||||
// damage the item
|
||||
newItem.durability -= (baseDamagePerSwing * swingDmgToFrameDmg).toFloat()
|
||||
if (newItem.durability <= 0)
|
||||
remove(newItem, 1)
|
||||
|
||||
//println("[ActorInventory] consumed; ${item.durability}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
fun contains(item: GameItem) = contains(item.dynamicID)
|
||||
fun contains(id: ItemID) =
|
||||
if (itemList.size == 0)
|
||||
false
|
||||
else
|
||||
itemList.binarySearch(id, DYNAMIC_ID) >= 0
|
||||
fun getByDynamicID(id: ItemID?): InventoryPair? {
|
||||
if (itemList.size == 0 || id == null)
|
||||
return null
|
||||
|
||||
val index = itemList.binarySearch(id, DYNAMIC_ID)
|
||||
if (index < 0)
|
||||
return null
|
||||
else
|
||||
return itemList[index]
|
||||
}
|
||||
private fun getByStaticID(id: ItemID): InventoryPair? {
|
||||
if (itemList.size == 0)
|
||||
return null
|
||||
|
||||
val index = itemList.binarySearch(id, STATIC_ID)
|
||||
if (index < 0)
|
||||
return null
|
||||
else
|
||||
return itemList[index]
|
||||
}
|
||||
private fun insertionSortLastElem(arr: ArrayList<InventoryPair>) {
|
||||
lock(ReentrantLock()) {
|
||||
var j = arr.lastIndex - 1
|
||||
val x = arr.last()
|
||||
while (j >= 0 && arr[j].item > x.item) {
|
||||
arr[j + 1] = arr[j]
|
||||
j -= 1
|
||||
}
|
||||
arr[j + 1] = x
|
||||
}
|
||||
}
|
||||
private val STATIC_ID = 41324534
|
||||
private val DYNAMIC_ID = 181643953
|
||||
private fun ArrayList<InventoryPair>.binarySearch(ID: ItemID, searchBy: Int): Int {
|
||||
// code from collections/Collections.kt
|
||||
var low = 0
|
||||
var high = this.size - 1
|
||||
|
||||
while (low <= high) {
|
||||
val mid = (low + high).ushr(1) // safe from overflows
|
||||
|
||||
val midVal = if (searchBy == STATIC_ID) this.get(mid).item.originalID else this.get(mid).item.dynamicID
|
||||
|
||||
if (ID > midVal)
|
||||
low = mid + 1
|
||||
else if (ID < midVal)
|
||||
high = mid - 1
|
||||
else
|
||||
return mid // key found
|
||||
}
|
||||
return -(low + 1) // key not found
|
||||
}
|
||||
inline fun lock(lock: Lock, body: () -> Unit) {
|
||||
lock.lock()
|
||||
try {
|
||||
body()
|
||||
}
|
||||
finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class InventoryPair(val item: GameItem, var amount: Int)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.itemproperties.GameItem
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-01-31.
|
||||
*/
|
||||
interface CanBeAnItem {
|
||||
|
||||
fun getItemWeight(): Double
|
||||
|
||||
fun stopUpdateAndDraw()
|
||||
|
||||
fun resumeUpdateAndDraw()
|
||||
|
||||
var itemData: GameItem
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-02-05.
|
||||
*/
|
||||
|
||||
object CreatureBuilder {
|
||||
|
||||
/**
|
||||
* @Param jsonFileName with extension
|
||||
*/
|
||||
operator fun invoke(world: GameWorld, module: String, jsonFileName: String): ActorWithPhysics {
|
||||
val actor = ActorWithPhysics(world, Actor.RenderOrder.MIDDLE)
|
||||
InjectCreatureRaw(actor.actorValue, module, jsonFileName)
|
||||
|
||||
|
||||
actor.actorValue[AVKey.__ACTION_TIMER] = 0.0
|
||||
|
||||
return actor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.Pixmap
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameworld.toUint
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
object DecodeTapestry {
|
||||
|
||||
val colourIndices64 = arrayOf(
|
||||
0x333.fourBitCol(),
|
||||
0x600.fourBitCol(),
|
||||
0xA36.fourBitCol(),
|
||||
0x636.fourBitCol(),
|
||||
0x73B.fourBitCol(),
|
||||
0x427.fourBitCol(),
|
||||
0x44C.fourBitCol(),
|
||||
0x038.fourBitCol(),
|
||||
0x47B.fourBitCol(),
|
||||
0x466.fourBitCol(),
|
||||
0x353.fourBitCol(),
|
||||
0x453.fourBitCol(),
|
||||
0x763.fourBitCol(),
|
||||
0xA63.fourBitCol(),
|
||||
0x742.fourBitCol(),
|
||||
0x000.fourBitCol(),
|
||||
0x666.fourBitCol(),
|
||||
0xA00.fourBitCol(),
|
||||
0xE2A.fourBitCol(),
|
||||
0xD2F.fourBitCol(),
|
||||
0x92F.fourBitCol(),
|
||||
0x548.fourBitCol(),
|
||||
0x32F.fourBitCol(),
|
||||
0x36F.fourBitCol(),
|
||||
0x588.fourBitCol(),
|
||||
0x390.fourBitCol(),
|
||||
0x5F0.fourBitCol(),
|
||||
0x684.fourBitCol(),
|
||||
0xBA2.fourBitCol(),
|
||||
0xE60.fourBitCol(),
|
||||
0x854.fourBitCol(),
|
||||
0x533.fourBitCol(),
|
||||
0x999.fourBitCol(),
|
||||
0xE00.fourBitCol(),
|
||||
0xE6A.fourBitCol(),
|
||||
0xE6F.fourBitCol(),
|
||||
0x848.fourBitCol(),
|
||||
0x62F.fourBitCol(),
|
||||
0x66F.fourBitCol(),
|
||||
0x4AF.fourBitCol(),
|
||||
0x5BA.fourBitCol(),
|
||||
0x8FE.fourBitCol(),
|
||||
0x7F8.fourBitCol(),
|
||||
0x9E0.fourBitCol(),
|
||||
0xFE0.fourBitCol(),
|
||||
0xEA0.fourBitCol(),
|
||||
0xC85.fourBitCol(),
|
||||
0xE55.fourBitCol(),
|
||||
0xCCC.fourBitCol(),
|
||||
0xFFF.fourBitCol(),
|
||||
0xFDE.fourBitCol(),
|
||||
0xEAF.fourBitCol(),
|
||||
0xA3B.fourBitCol(),
|
||||
0x96F.fourBitCol(),
|
||||
0xAAF.fourBitCol(),
|
||||
0x7AF.fourBitCol(),
|
||||
0x3DF.fourBitCol(),
|
||||
0xBFF.fourBitCol(),
|
||||
0xBFB.fourBitCol(),
|
||||
0xAF6.fourBitCol(),
|
||||
0xFEB.fourBitCol(),
|
||||
0xFD7.fourBitCol(),
|
||||
0xE96.fourBitCol(),
|
||||
0xEBA.fourBitCol()
|
||||
)
|
||||
|
||||
val colourIndices16 = arrayOf(
|
||||
0x000.fourBitCol(),
|
||||
0xfff.fourBitCol(),
|
||||
0x666.fourBitCol(),
|
||||
0xccc.fourBitCol(),
|
||||
0xfe0.fourBitCol(),
|
||||
0xe60.fourBitCol(),
|
||||
0xe00.fourBitCol(),
|
||||
0xe2a.fourBitCol(),
|
||||
0x427.fourBitCol(),
|
||||
0x32f.fourBitCol(),
|
||||
0x4af.fourBitCol(),
|
||||
0x5f0.fourBitCol(),
|
||||
0x390.fourBitCol(),
|
||||
0x353.fourBitCol(),
|
||||
0x533.fourBitCol(),
|
||||
0xa63.fourBitCol()
|
||||
)
|
||||
|
||||
private fun Int.fourBitCol() = Color(
|
||||
this.and(0xF00).shl(20) or this.and(0xF00).shl(16) or
|
||||
this.and(0x0F0).shl(16) or this.and(0x0F0).shl(12) or
|
||||
this.and(0x00F).shl(12) or this.and(0x00F).shl(8) or
|
||||
0xFF
|
||||
)
|
||||
|
||||
val MAGIC = "TEAF".toByteArray(charset = Charset.forName("US-ASCII"))
|
||||
val FORMAT_16 = 1
|
||||
val FORMAT_64 = 2
|
||||
|
||||
operator fun invoke(fileObj: File): TapestryObject {
|
||||
fun magicMismatch(magic: ByteArray, array: ByteArray): Boolean {
|
||||
return !Arrays.equals(array.sliceArray(0..magic.lastIndex), magic)
|
||||
}
|
||||
|
||||
val file = fileObj.readBytes()
|
||||
|
||||
val magic = file.copyOfRange(0, 4)
|
||||
|
||||
if (magicMismatch(MAGIC, magic))
|
||||
throw RuntimeException("Invalid file -- type mismatch: expected header " +
|
||||
"${MAGIC[0]} ${MAGIC[1]} ${MAGIC[2]} ${MAGIC[3]}; got " +
|
||||
"${magic[0]} ${magic[1]} ${magic[2]} ${magic[3]}")
|
||||
|
||||
val colourModel = file[4].toUint()
|
||||
|
||||
if (colourModel != FORMAT_16 && colourModel != FORMAT_64)
|
||||
throw RuntimeException("Invalid colour model: $colourModel")
|
||||
|
||||
val width = file[7].toUint().shl(8) + file[6].toUint()
|
||||
|
||||
val artNameBytes = ArrayList<Byte>()
|
||||
val authorNameBytes = ArrayList<Byte>()
|
||||
|
||||
var readCounter = 8
|
||||
|
||||
while (file[readCounter] != 0x00.toByte()) {
|
||||
artNameBytes.add(file[readCounter])
|
||||
readCounter++
|
||||
}
|
||||
|
||||
readCounter++ // jump over null terminator
|
||||
|
||||
while (file[readCounter] != 0x00.toByte()) {
|
||||
authorNameBytes.add(file[readCounter])
|
||||
readCounter++
|
||||
}
|
||||
|
||||
readCounter++ // jump over null terminator
|
||||
|
||||
|
||||
|
||||
val artName = String(artNameBytes.toByteArray(), charset = Charset.forName("UTF-8"))
|
||||
val authorName = String(authorNameBytes.toByteArray(), charset = Charset.forName("UTF-8"))
|
||||
|
||||
val imageDataSize = file.size - readCounter
|
||||
val height = imageDataSize / width
|
||||
val outImageData = Pixmap(width, height, Pixmap.Format.RGBA8888)
|
||||
val counterOffset = readCounter
|
||||
while (readCounter < file.size) {
|
||||
val ofs = readCounter - counterOffset
|
||||
val palIndex = file[readCounter].toUint()
|
||||
|
||||
if (colourModel == FORMAT_16) {
|
||||
outImageData.setColor(colourIndices16[palIndex])
|
||||
outImageData.drawPixel(ofs % width, ofs / width)
|
||||
}
|
||||
else {
|
||||
outImageData.setColor(colourIndices64[palIndex])
|
||||
outImageData.drawPixel(ofs % width, ofs / width)
|
||||
}
|
||||
|
||||
readCounter++
|
||||
}
|
||||
|
||||
return TapestryObject((Terrarum.ingame!! as Ingame).world, outImageData, artName, authorName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.itemproperties.GameItem
|
||||
import net.torvald.terrarum.itemproperties.ItemCodex
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-03-15.
|
||||
*/
|
||||
open class DroppedItem(world: GameWorld, private val item: GameItem) : ActorWithPhysics(world, RenderOrder.MIDTOP) {
|
||||
|
||||
init {
|
||||
if (item.dynamicID >= ItemCodex.ACTORID_MIN)
|
||||
throw RuntimeException("Attempted to create DroppedItem actor of a real actor; the real actor must be dropped instead.")
|
||||
|
||||
isVisible = true
|
||||
|
||||
avBaseMass = if (item.dynamicID < BlockCodex.TILE_UNIQUE_MAX)
|
||||
BlockCodex[item.dynamicID].density / 1000.0
|
||||
else
|
||||
ItemCodex[item.dynamicID].mass
|
||||
|
||||
scale = ItemCodex[item.dynamicID].scale
|
||||
}
|
||||
|
||||
override fun update(delta: Float) {
|
||||
super.update(delta)
|
||||
}
|
||||
|
||||
override fun drawGlow(batch: SpriteBatch) {
|
||||
super.drawGlow(batch)
|
||||
}
|
||||
|
||||
override fun drawBody(batch: SpriteBatch) {
|
||||
super.drawBody(batch)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-06-17.
|
||||
*/
|
||||
open class FixtureBase(world: GameWorld, physics: Boolean = true) :
|
||||
ActorWithPhysics(world, RenderOrder.BEHIND, immobileBody = true, usePhysics = physics) {
|
||||
/**
|
||||
* 0: Open
|
||||
* 1: Blocked
|
||||
* 2: Platform; can be stood on, press DOWN to go down. Also allows other blocks can be places on top of it (e.g. torch)
|
||||
* 3: Wall_left; blocks rightward movement
|
||||
* 4: Wall_right: blocks leftward movement
|
||||
* 5: Same as 2 but player CANNOT go down
|
||||
* For example, flag of 4 is good for tables; player can stand on, which means
|
||||
* downward movement is blocked within the fixtures' AABB.
|
||||
*/
|
||||
var collisionFlag: Int = 0
|
||||
|
||||
companion object {
|
||||
val COLLISION_OPEN = 0
|
||||
val COLLISION_BLOCKED = 1
|
||||
val COLLISION_PLATFORM = 2
|
||||
val COLLISION_WALL_LEFT = 3
|
||||
val COLLISION_WALL_RIGHT = 4
|
||||
val COLLISION_PLATFORM_NOGODOWN = 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.gameactors.Hitbox
|
||||
import net.torvald.terrarum.gameactors.Luminous
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-06-17.
|
||||
*/
|
||||
internal class FixtureTikiTorch(world: GameWorld) : FixtureBase(world), Luminous {
|
||||
|
||||
override var color: Color
|
||||
get() = BlockCodex[Block.TORCH].luminosity
|
||||
set(value) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override val lightBoxList: ArrayList<Hitbox>
|
||||
|
||||
init {
|
||||
density = 1200.0
|
||||
|
||||
setHitboxDimension(10, 24, 0, 0)
|
||||
|
||||
lightBoxList = ArrayList(1)
|
||||
lightBoxList.add(Hitbox(3.0, 0.0, 4.0, 3.0))
|
||||
|
||||
makeNewSprite(TextureRegionPack(ModMgr.getGdxFile("basegame", "sprites/fixtures/tiki_torch.tga"), 10, 27))
|
||||
sprite!!.delay = 0.2f
|
||||
sprite!!.setRowsAndFrames(1, 1)
|
||||
|
||||
actorValue[AVKey.BASEMASS] = 1.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.random.HQRNG
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.WorldTime
|
||||
|
||||
typealias AnyPlayer = HistoricalFigure
|
||||
|
||||
/**
|
||||
* An actor (NPC) which has life and death,
|
||||
* though death might not exist if it has achieved immortality :)
|
||||
*
|
||||
* NOTE: all canonical NPCs are must be HistoricalFigure!! (double excl mark, bitch)
|
||||
*
|
||||
* Created by minjaesong on 2016-10-10.
|
||||
*/
|
||||
open class HistoricalFigure(
|
||||
world: GameWorld,
|
||||
val born: GameDate,
|
||||
val dead: GameDate? = null,
|
||||
realAirFriction: Boolean = false,
|
||||
usePhysics: Boolean = true
|
||||
) : ActorWithPhysics(world, RenderOrder.MIDDLE, realAirFriction, usePhysics) {
|
||||
|
||||
var historicalFigureIdentifier: Int = generateHistoricalFigureIdentifier()
|
||||
internal set
|
||||
|
||||
private fun generateHistoricalFigureIdentifier(): Int {
|
||||
fun hasCollision(value: Int) =
|
||||
try {
|
||||
(Terrarum.ingame!! as Ingame).historicalFigureIDBucket.contains(value)
|
||||
}
|
||||
catch (gameNotInitialisedException: KotlinNullPointerException) {
|
||||
false
|
||||
}
|
||||
|
||||
var ret: Int
|
||||
do {
|
||||
ret = HQRNG().nextInt() // set new ID
|
||||
} while (hasCollision(ret)) // check for collision
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
this.actorValue["_bornyear"] = born.year
|
||||
this.actorValue["_borndays"] = born.yearlyDay
|
||||
|
||||
if (dead != null) {
|
||||
this.actorValue["_deadyear"] = dead.year
|
||||
this.actorValue["_deaddays"] = dead.yearlyDay
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class GameDate(val year: Int, val yearlyDay: Int) {
|
||||
operator fun plus(other: GameDate): GameDate {
|
||||
var newyd = this.yearlyDay + other.yearlyDay
|
||||
var newy = this.year + other.year
|
||||
|
||||
if (newyd > WorldTime.YEAR_DAYS) {
|
||||
newyd -= WorldTime.YEAR_DAYS
|
||||
newy += 1
|
||||
}
|
||||
|
||||
return GameDate(newy, newyd)
|
||||
}
|
||||
|
||||
operator fun minus(other: GameDate): GameDate {
|
||||
var newyd = this.yearlyDay - other.yearlyDay
|
||||
var newy = this.year - other.year
|
||||
|
||||
if (newyd < 0) {
|
||||
newyd += WorldTime.YEAR_DAYS
|
||||
newy -= 1
|
||||
}
|
||||
|
||||
return GameDate(newy, newyd)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameactors.AIControlled
|
||||
import net.torvald.terrarum.gameactors.ai.ActorAI
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.itemproperties.GameItem
|
||||
import net.torvald.terrarum.itemproperties.Material
|
||||
|
||||
/**
|
||||
* @param ai AI class. Use LuaAIWrapper for Lua script
|
||||
*
|
||||
* Created by minjaesong on 2016-01-31.
|
||||
*/
|
||||
open class HumanoidNPC(
|
||||
world: GameWorld,
|
||||
override val ai: ActorAI, // it's there for written-in-Kotlin, "hard-wired" AIs
|
||||
born: GameDate,
|
||||
usePhysics: Boolean = true,
|
||||
forceAssignRefID: Int? = null
|
||||
) : ActorHumanoid(world, born, usePhysics = usePhysics), AIControlled, CanBeAnItem {
|
||||
|
||||
companion object {
|
||||
val DEFAULT_COLLISION_TYPE = COLLISION_DYNAMIC
|
||||
}
|
||||
|
||||
init {
|
||||
collisionType = DEFAULT_COLLISION_TYPE
|
||||
}
|
||||
|
||||
// we're having GameItem data so that this class could be somewhat universal
|
||||
override var itemData: GameItem = object : GameItem() {
|
||||
override var dynamicID = referenceID ?: forceAssignRefID!!
|
||||
override val originalID = dynamicID
|
||||
override val isUnique = true
|
||||
override var baseMass: Double
|
||||
get() = actorValue.getAsDouble(AVKey.BASEMASS)!!
|
||||
set(value) { actorValue[AVKey.BASEMASS] = value }
|
||||
override var baseToolSize: Double? = 0.0
|
||||
override var scale: Double
|
||||
get() = actorValue.getAsDouble(AVKey.SCALE)!!
|
||||
set(value) {
|
||||
actorValue[AVKey.SCALE] = value
|
||||
}
|
||||
override var inventoryCategory = "npc"
|
||||
override val originalName: String = actorValue.getAsString(AVKey.NAME) ?: "NPC"
|
||||
override var stackable = true
|
||||
override val isDynamic = false
|
||||
override val material = Material(0,0,0,0,0,0,0,0,0,0.0)
|
||||
|
||||
override fun secondaryUse(delta: Float): Boolean {
|
||||
try {
|
||||
// place the actor to the world
|
||||
this@HumanoidNPC.setPosition(Terrarum.mouseX, Terrarum.mouseY)
|
||||
Terrarum.ingame!!.addNewActor(this@HumanoidNPC)
|
||||
// successful
|
||||
return true
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemWeight(): Double {
|
||||
return mass
|
||||
}
|
||||
|
||||
override fun stopUpdateAndDraw() {
|
||||
isUpdate = false
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
override fun resumeUpdateAndDraw() {
|
||||
isUpdate = true
|
||||
isVisible = true
|
||||
}
|
||||
|
||||
override fun update(delta: Float) {
|
||||
ai.update(this, delta)
|
||||
super.update(delta)
|
||||
}
|
||||
|
||||
override fun moveLeft(amount: Float) { // hit the buttons on the controller box
|
||||
axisX = -amount
|
||||
}
|
||||
|
||||
override fun moveRight(amount: Float) { // hit the buttons on the controller box
|
||||
axisX = amount
|
||||
}
|
||||
|
||||
override fun moveUp(amount: Float) { // hit the buttons on the controller box
|
||||
axisY = -amount
|
||||
}
|
||||
|
||||
override fun moveDown(amount: Float) { // hit the buttons on the controller box
|
||||
axisY = amount
|
||||
}
|
||||
|
||||
override fun moveJump(amount: Float) { // hit the buttons on the controller box
|
||||
isJumpDown = true
|
||||
}
|
||||
|
||||
/** fly toward arbitrary angle WARNING: the map is looped! */
|
||||
override fun moveTo(bearing: Double) {
|
||||
// if your NPC should fly, override this
|
||||
throw UnsupportedOperationException("Humans cannot fly :p")
|
||||
}
|
||||
|
||||
/** fly toward arbitrary coord WARNING: the map is looped! */
|
||||
override fun moveTo(toX: Double, toY: Double) {
|
||||
// if your NPC should fly, override this
|
||||
throw UnsupportedOperationException("Humans cannot fly :p")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.utils.JsonFetcher
|
||||
import net.torvald.random.Fudge3
|
||||
import net.torvald.terrarum.langpack.Lang
|
||||
import com.google.gson.JsonObject
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.gameactors.ActorValue
|
||||
import java.security.SecureRandom
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-03-25.
|
||||
*/
|
||||
object InjectCreatureRaw {
|
||||
|
||||
private const val JSONMULT = "mult" // one appears in JSON files
|
||||
|
||||
/**
|
||||
* 'Injects' creature raw ActorValue to the ActorValue reference provided.
|
||||
*
|
||||
* @param actorValueRef ActorValue object to be injected.
|
||||
* @param jsonFileName with extension
|
||||
*/
|
||||
operator fun invoke(actorValueRef: ActorValue, module: String, jsonFileName: String) {
|
||||
val jsonObj = JsonFetcher(ModMgr.getPath(module, "creatures/$jsonFileName"))
|
||||
|
||||
val elementsInt = arrayOf(AVKey.BASEHEIGHT, AVKey.TOOLSIZE, AVKey.ENCUMBRANCE)
|
||||
val elementsString = arrayOf(AVKey.RACENAME, AVKey.RACENAMEPLURAL)
|
||||
val elementsDouble = arrayOf(AVKey.BASEMASS, AVKey.ACCEL)
|
||||
val elementsDoubleVariable = arrayOf(AVKey.STRENGTH, AVKey.SPEED, AVKey.JUMPPOWER, AVKey.SCALE)
|
||||
val elementsBoolean = arrayOf(AVKey.INTELLIGENT)
|
||||
// val elementsMultiplyFromOne = arrayOf()
|
||||
|
||||
setAVInts(actorValueRef, elementsInt, jsonObj)
|
||||
setAVStrings(actorValueRef, elementsString, jsonObj)
|
||||
setAVDoubles(actorValueRef, elementsDouble, jsonObj)
|
||||
setAVDoublesVariable(actorValueRef, elementsDoubleVariable, jsonObj)
|
||||
// setAVMultiplyFromOne(actorValueRef, elementsMultiplyFromOne, jsonObj)
|
||||
setAVBooleans(actorValueRef, elementsBoolean, jsonObj)
|
||||
|
||||
actorValueRef[AVKey.ACCEL] = ActorHumanoid.WALK_ACCEL_BASE
|
||||
actorValueRef[AVKey.ACCELBUFF] = 1.0
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and set actor values that have 'variable' appended. E.g. strength
|
||||
* @param avRef
|
||||
* *
|
||||
* @param elemSet
|
||||
* *
|
||||
* @param jsonObject
|
||||
*/
|
||||
private fun setAVDoublesVariable(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
|
||||
for (s in elemSet) {
|
||||
val baseValue = jsonObject.get(s).asDouble
|
||||
// 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 + JSONMULT).asJsonArray.get(varSelected).asInt
|
||||
val realValue = baseValue * multiplier / 100.0
|
||||
|
||||
avRef[s] = realValue
|
||||
avRef[s + "buff"] = 1.0 // buffed value: 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 double actor values
|
||||
* @param avRef
|
||||
* *
|
||||
* @param elemSet
|
||||
* *
|
||||
* @param jsonObject
|
||||
*/
|
||||
private fun setAVDoubles(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
|
||||
for (s in elemSet) {
|
||||
avRef[s] = jsonObject.get(s).asDouble
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and set int actor values
|
||||
* @param avRef
|
||||
* *
|
||||
* @param elemSet
|
||||
* *
|
||||
* @param jsonObject
|
||||
*/
|
||||
private fun setAVInts(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
|
||||
for (s in elemSet) {
|
||||
avRef[s] = jsonObject.get(s).asInt
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and set actor values that should multiplier be applied to the base value of 1.
|
||||
* @param avRef
|
||||
* *
|
||||
* @param elemSet
|
||||
* *
|
||||
* @param jsonObject
|
||||
*/
|
||||
private fun setAVMultiplyFromOne(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
|
||||
for (s in elemSet) {
|
||||
val baseValue = 1.0
|
||||
// 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 / 100.0
|
||||
|
||||
avRef[s] = realValue
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAVBooleans(avRef: ActorValue, elemSet: Array<String>, jsonObject: JsonObject) {
|
||||
for (s in elemSet) {
|
||||
avRef[s] = jsonObject.get(s).asBoolean
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-02-20.
|
||||
*/
|
||||
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<Long>?
|
||||
fun addHouseTile(x: Int, y: Int)
|
||||
fun removeHouseTile(x: Int, y: Int)
|
||||
fun clearHouseDesignation()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||
import net.torvald.terrarum.Second
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.ActorWithPhysics.Companion.SI_TO_GAME_ACC
|
||||
import net.torvald.terrarum.worlddrawer.FeaturesDrawer.TILE_SIZE
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.floorInt
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
import net.torvald.terrarum.gameactors.Hitbox
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import org.dyn4j.geometry.Vector2
|
||||
|
||||
/**
|
||||
* Actors with static sprites and very simple physics
|
||||
*
|
||||
* Created by minjaesong on 2017-01-20.
|
||||
*/
|
||||
open class ParticleBase(renderOrder: Actor.RenderOrder, val despawnUponCollision: Boolean, maxLifeTime: Second? = null) : Runnable {
|
||||
|
||||
/** Will NOT actually delete from the CircularArray */
|
||||
@Volatile var flagDespawn = false
|
||||
|
||||
override fun run() = update(Terrarum.deltaTime)
|
||||
|
||||
var isNoSubjectToGrav = false
|
||||
var dragCoefficient = 3.0
|
||||
|
||||
private val lifetimeMax = maxLifeTime ?: 5f
|
||||
private var lifetimeCounter = 0f
|
||||
|
||||
open val velocity = Vector2(0.0, 0.0)
|
||||
open val hitbox = Hitbox(0.0, 0.0, 0.0, 0.0)
|
||||
|
||||
open lateinit var body: TextureRegion // you might want to use SpriteAnimation
|
||||
open var glow: TextureRegion? = null
|
||||
|
||||
init {
|
||||
|
||||
}
|
||||
|
||||
fun update(delta: Float) {
|
||||
if (!flagDespawn) {
|
||||
lifetimeCounter += delta
|
||||
if (despawnUponCollision) {
|
||||
if (velocity.isZero ||
|
||||
// simple stuck check
|
||||
BlockCodex[(Terrarum.ingame!! as Ingame).world.getTileFromTerrain(
|
||||
hitbox.canonicalX.div(TILE_SIZE).floorInt(),
|
||||
hitbox.canonicalY.div(TILE_SIZE).floorInt()
|
||||
) ?: Block.STONE].isSolid) {
|
||||
flagDespawn = true
|
||||
}
|
||||
}
|
||||
|
||||
if (lifetimeCounter >= lifetimeMax) {
|
||||
flagDespawn = true
|
||||
}
|
||||
|
||||
// gravity, winds, etc. (external forces)
|
||||
if (!isNoSubjectToGrav) {
|
||||
velocity += (Terrarum.ingame!! as Ingame).world.gravitation / dragCoefficient * SI_TO_GAME_ACC
|
||||
}
|
||||
|
||||
|
||||
// combine external forces
|
||||
hitbox.translate(velocity)
|
||||
}
|
||||
}
|
||||
|
||||
fun drawBody(batch: SpriteBatch) {
|
||||
if (!flagDespawn) {
|
||||
batch.draw(body, hitbox.startX.toFloat(), hitbox.startY.toFloat(), hitbox.width.toFloat(), hitbox.height.toFloat())
|
||||
}
|
||||
}
|
||||
|
||||
fun drawGlow(batch: SpriteBatch) {
|
||||
if (!flagDespawn && glow != null) {
|
||||
batch.draw(glow, hitbox.startX.toFloat(), hitbox.startY.toFloat(), hitbox.width.toFloat(), hitbox.height.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.Pixmap
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||
import net.torvald.random.HQRNG
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.Second
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2017-12-18.
|
||||
*/
|
||||
class ParticleMegaRain(posX: Double, posY: Double) : ParticleBase(Actor.RenderOrder.BEHIND, true, 3.2f) {
|
||||
|
||||
init {
|
||||
body = MegaRainGovernor.get()
|
||||
val w = body.regionWidth.toDouble()
|
||||
val h = body.regionHeight.toDouble()
|
||||
hitbox.setFromWidthHeight(
|
||||
posX - w.times(0.5),
|
||||
posY - h.times(0.5),
|
||||
w, h
|
||||
)
|
||||
|
||||
velocity.y = 11.5 * ActorWithPhysics.SI_TO_GAME_VEL
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object MegaRainGovernor {
|
||||
|
||||
private var reseedTimer = 0f
|
||||
var reseedTime: Second = 90f
|
||||
|
||||
private val body = Pixmap(ModMgr.getGdxFile("basegame", "weathers/raindrop.tga"))
|
||||
private lateinit var bodies: Array<TextureRegion>
|
||||
|
||||
private var withdrawCounter = 0
|
||||
|
||||
init {
|
||||
seed()
|
||||
}
|
||||
|
||||
private fun seed() {
|
||||
val w = body.width
|
||||
val h = body.height
|
||||
|
||||
bodies = Array(1024) {
|
||||
//val pixmap = Pixmap(Terrarum.WIDTH * 2, Terrarum.HEIGHT / 4, Pixmap.Format.RGBA8888)
|
||||
val pixmap = Pixmap(64, 64, Pixmap.Format.RGBA8888)
|
||||
|
||||
val rng = HQRNG()
|
||||
|
||||
repeat(rng.nextInt(2) + 3) { // 3 or 4
|
||||
val rndX = rng.nextInt(pixmap.width - body.width)
|
||||
val rndY = rng.nextInt(pixmap.height - body.height)
|
||||
|
||||
pixmap.drawPixmap(body, rndX, rndY)
|
||||
}
|
||||
|
||||
// return composed (mega)pixmap
|
||||
val region = TextureRegion(Texture(pixmap))
|
||||
region.flip(false, true)
|
||||
|
||||
/*return*/region
|
||||
}
|
||||
|
||||
// randomise
|
||||
bodies.shuffle()
|
||||
}
|
||||
|
||||
fun get(): TextureRegion {
|
||||
if (withdrawCounter >= bodies.size) {
|
||||
withdrawCounter = 0
|
||||
//bodies.shuffle() // if pre-rendered random set is sufficiently large, it'd look random enough
|
||||
}
|
||||
|
||||
return bodies[withdrawCounter++]
|
||||
}
|
||||
|
||||
@Deprecated("re-seeding freezes the game a little and large enough randomnesses ought to be good")
|
||||
fun update(delta: Float) {
|
||||
if (reseedTimer >= reseedTime) {
|
||||
seed()
|
||||
reseedTimer -= reseedTime
|
||||
}
|
||||
|
||||
reseedTimer += delta
|
||||
}
|
||||
|
||||
fun resize() {
|
||||
seed()
|
||||
withdrawCounter = 0
|
||||
reseedTimer = 0f
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun Array<TextureRegion>.shuffle() {
|
||||
for (i in this.size - 1 downTo 1) {
|
||||
val rndIndex = (Math.random() * (i + 1)).toInt()
|
||||
|
||||
val t = this[rndIndex]
|
||||
this[rndIndex] = this[i]
|
||||
this[i] = t
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2017-01-20.
|
||||
*/
|
||||
class ParticleTestRain(posX: Double, posY: Double) : ParticleBase(Actor.RenderOrder.BEHIND, true, 6f) {
|
||||
|
||||
init {
|
||||
body = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "weathers/raindrop.tga")))
|
||||
val w = body.regionWidth.toDouble()
|
||||
val h = body.regionHeight.toDouble()
|
||||
hitbox.setFromWidthHeight(
|
||||
posX - w.times(0.5),
|
||||
posY - h.times(0.5),
|
||||
w, h
|
||||
)
|
||||
|
||||
velocity.y = 10.0
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-03-05.
|
||||
*/
|
||||
class PhysTestBall(world: GameWorld) : ActorWithPhysics(world, RenderOrder.MIDDLE, immobileBody = true) {
|
||||
|
||||
private var color = Color.GOLD
|
||||
|
||||
init {
|
||||
setHitboxDimension(16, 16, 0, 0)
|
||||
avBaseMass = 10.0
|
||||
density = 200.0
|
||||
|
||||
color = RoguelikeRandomiser.composeColourFrom(RoguelikeRandomiser.POTION_PRIMARY_COLSET)
|
||||
}
|
||||
|
||||
override fun drawBody(batch: SpriteBatch) {
|
||||
Terrarum.inShapeRenderer {
|
||||
it.color = color
|
||||
it.circle(
|
||||
hitbox.startX.toFloat() - 1f,
|
||||
hitbox.startY.toFloat() - 1f,
|
||||
hitbox.width.toFloat()
|
||||
)
|
||||
|
||||
it.circle(
|
||||
hitbox.startX.toFloat() + (Terrarum.ingame!! as Ingame).world.width * TILE_SIZE - 1f,
|
||||
hitbox.startY.toFloat() - 1f,
|
||||
hitbox.width.toFloat()
|
||||
)
|
||||
|
||||
it.circle(
|
||||
hitbox.startX.toFloat() - (Terrarum.ingame!! as Ingame).world.width * TILE_SIZE - 1f,
|
||||
hitbox.startY.toFloat() - 1f,
|
||||
hitbox.width.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
//println(moveDelta)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.Input
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.gameactors.Controllable
|
||||
import net.torvald.terrarum.gameactors.Hitbox
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2018-01-17.
|
||||
*/
|
||||
class PhysTestLuarLander(world: GameWorld) : ActorWithPhysics(world, RenderOrder.MIDTOP), Controllable {
|
||||
|
||||
private val texture = Texture(ModMgr.getGdxFile("basegame", "sprites/phystest_lunarlander.tga"))
|
||||
|
||||
override val hitbox: Hitbox
|
||||
|
||||
init {
|
||||
hitbox = Hitbox(0.0, 0.0, 0.0, 0.0)
|
||||
setHitboxDimension(texture.width, texture.height, 0, 0)
|
||||
|
||||
actorValue[AVKey.SPEED] = 8.0
|
||||
avBaseMass = 18650.0
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
super.run()
|
||||
}
|
||||
|
||||
override fun update(delta: Float) {
|
||||
super.update(delta)
|
||||
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
|
||||
controllerMoveDelta!!.y = avSpeedCap
|
||||
}
|
||||
}
|
||||
|
||||
override fun keyDown(keycode: Int): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun drawGlow(batch: SpriteBatch) {
|
||||
}
|
||||
|
||||
override fun drawBody(batch: SpriteBatch) {
|
||||
batch.color = Color.WHITE
|
||||
batch.draw(texture, hitbox.startX.toFloat(), hitbox.endY.toFloat(), hitbox.width.toFloat(), -hitbox.height.toFloat())
|
||||
}
|
||||
|
||||
override fun onActorValueChange(key: String, value: Any?) {
|
||||
super.onActorValueChange(key, value)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
super.dispose()
|
||||
texture.dispose()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.gameactors.Controllable
|
||||
|
||||
/**
|
||||
* A wrapper to support instant player changing (or possessing other NPCs maybe)
|
||||
*
|
||||
* @param actor : here you 'attach' the actor you wish to control
|
||||
* Created by minjaesong on 2016-10-23.
|
||||
*/
|
||||
class PlayableActorDelegate(val actor: ActorHumanoid) {
|
||||
|
||||
init {
|
||||
if (actor !is Controllable)
|
||||
throw IllegalArgumentException("Player must be 'Controllable'!")
|
||||
}
|
||||
|
||||
|
||||
fun update(delta: Float) {
|
||||
//val oldTilewisePos = actor.hIntTilewiseHitbox
|
||||
|
||||
actor.update(delta)
|
||||
|
||||
// fire lightmap recalculate event upon tilewise pos change
|
||||
//val newTilewisePos = actor.hIntTilewiseHitbox
|
||||
//if (oldTilewisePos != newTilewisePos) {
|
||||
// LightmapRenderer.fireRecalculateEvent()
|
||||
//}
|
||||
// not going to work: think about stationery tiki torches, global lights, etc
|
||||
}
|
||||
}
|
||||
35
src/net/torvald/terrarum/modulebasegame/gameactors/Player.kt
Normal file
35
src/net/torvald/terrarum/modulebasegame/gameactors/Player.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
|
||||
|
||||
/**
|
||||
* Game player (YOU!)
|
||||
*
|
||||
* Created by minjaesong on 2015-12-31.
|
||||
*/
|
||||
|
||||
class Player(world: GameWorld, born: GameDate) : ActorHumanoid(world, born) {
|
||||
|
||||
companion object {
|
||||
@Transient const val PLAYER_REF_ID: Int = 0x91A7E2
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new Player instance with empty elements (sprites, actorvalue, etc.).
|
||||
|
||||
* **Use PlayerFactory to build player!**
|
||||
|
||||
* @throws SlickException
|
||||
*/
|
||||
init {
|
||||
referenceID = PLAYER_REF_ID // forcibly set ID
|
||||
density = BASE_DENSITY
|
||||
collisionType = COLLISION_KINEMATIC
|
||||
}
|
||||
|
||||
override fun update(delta: Float) {
|
||||
super.update(delta)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-02-03.
|
||||
*/
|
||||
object PlayerBuilder {
|
||||
|
||||
operator fun invoke(): Actor {
|
||||
val p: Actor = Player((Terrarum.ingame!! as Ingame).world, (Terrarum.ingame!! as Ingame).world.time.currentTimeAsGameDate)
|
||||
InjectCreatureRaw(p.actorValue, "basegame", "CreatureHuman.json")
|
||||
|
||||
// attach sprite
|
||||
|
||||
// do etc.
|
||||
p.actorValue[AVKey.__PLAYER_QUICKSLOTSEL] = 0
|
||||
p.actorValue[AVKey.__ACTION_TIMER] = 0.0
|
||||
p.actorValue[AVKey.ACTION_INTERVAL] = ActorHumanoid.BASE_ACTION_INTERVAL
|
||||
|
||||
return p
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameactors.ai.NullAI
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
|
||||
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-03-25.
|
||||
*/
|
||||
object PlayerBuilderCynthia {
|
||||
|
||||
operator fun invoke(): ActorWithPhysics {
|
||||
//val p: Player = Player(GameDate(100, 143)) // random value thrown
|
||||
val p: HumanoidNPC = HumanoidNPC(
|
||||
(Terrarum.ingame!! as Ingame).world,
|
||||
NullAI(),
|
||||
GameDate(100, 143)) // random value thrown
|
||||
InjectCreatureRaw(p.actorValue, "basegame", "CreatureHuman.json")
|
||||
|
||||
|
||||
p.actorValue[AVKey.__PLAYER_QUICKSLOTSEL] = 0
|
||||
p.actorValue[AVKey.__ACTION_TIMER] = 0.0
|
||||
p.actorValue[AVKey.ACTION_INTERVAL] = ActorHumanoid.BASE_ACTION_INTERVAL
|
||||
p.actorValue[AVKey.NAME] = "Cynthia"
|
||||
|
||||
|
||||
p.makeNewSprite(TextureRegionPack(ModMgr.getGdxFile("basegame", "sprites/test_player_2.tga"), 26, 42))
|
||||
p.sprite!!.delay = 0.2f
|
||||
p.sprite!!.setRowsAndFrames(1, 1)
|
||||
|
||||
p.setHitboxDimension(15, p.actorValue.getAsInt(AVKey.BASEHEIGHT) ?: ActorHumanoid.BASE_HEIGHT, 9, 0)
|
||||
|
||||
p.setPosition(4096.0 * FeaturesDrawer.TILE_SIZE, 300.0 * FeaturesDrawer.TILE_SIZE)
|
||||
|
||||
|
||||
|
||||
|
||||
p.referenceID = 321321321
|
||||
|
||||
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameactors.faction.FactionFactory
|
||||
import net.torvald.terrarum.itemproperties.ItemCodex
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-02-03.
|
||||
*/
|
||||
|
||||
object PlayerBuilderSigrid {
|
||||
|
||||
operator fun invoke(): Player {
|
||||
val p = Player((Terrarum.ingame!! as Ingame).world, GameDate(-2147483648, 0)) // XD
|
||||
|
||||
p.referenceID = 0x51621D // the only constant of this procedural universe
|
||||
p.historicalFigureIdentifier = 0x51621D // the only constant of this procedural universe
|
||||
|
||||
|
||||
p.makeNewSprite(TextureRegionPack(ModMgr.getGdxFile("basegame", "sprites/test_player.tga"), 28, 51))
|
||||
p.sprite!!.delay = 0.2f
|
||||
p.sprite!!.setRowsAndFrames(1, 1)
|
||||
|
||||
p.makeNewSpriteGlow(TextureRegionPack(ModMgr.getGdxFile("basegame", "sprites/test_player_glow.tga"), 28, 51))
|
||||
p.spriteGlow!!.delay = 0.2f
|
||||
p.spriteGlow!!.setRowsAndFrames(1, 1)
|
||||
|
||||
p.actorValue[AVKey.SCALE] = 1.0
|
||||
p.actorValue[AVKey.SPEED] = 4.0
|
||||
p.actorValue[AVKey.SPEEDBUFF] = 1.0
|
||||
p.actorValue[AVKey.ACCEL] = ActorHumanoid.WALK_ACCEL_BASE
|
||||
p.actorValue[AVKey.ACCELBUFF] = 1.0
|
||||
p.actorValue[AVKey.JUMPPOWER] = 13.0
|
||||
|
||||
p.actorValue[AVKey.BASEMASS] = 80.0
|
||||
p.actorValue[AVKey.SCALEBUFF] = 1.0 // 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 // this is test character, after all.
|
||||
p.actorValue[AVKey.ENCUMBRANCE] = 1000
|
||||
p.actorValue[AVKey.BASEHEIGHT] = 46
|
||||
|
||||
p.actorValue[AVKey.NAME] = "Sigrid"
|
||||
|
||||
p.actorValue[AVKey.INTELLIGENT] = true
|
||||
|
||||
//p.actorValue[AVKey.LUMR] = 0.84
|
||||
//p.actorValue[AVKey.LUMG] = 0.93
|
||||
//p.actorValue[AVKey.LUMB] = 1.37
|
||||
p.actorValue[AVKey.LUMA] = 1.93
|
||||
|
||||
p.actorValue[AVKey.BASEDEFENCE] = 141
|
||||
|
||||
p.actorValue[AVKey.__PLAYER_QUICKSLOTSEL] = 0
|
||||
p.actorValue[AVKey.__ACTION_TIMER] = 0.0
|
||||
p.actorValue[AVKey.ACTION_INTERVAL] = ActorHumanoid.BASE_ACTION_INTERVAL
|
||||
p.actorValue["__aimhelper"] = true // TODO when you'll gonna implement it?
|
||||
|
||||
p.setHitboxDimension(15, p.actorValue.getAsInt(AVKey.BASEHEIGHT)!!, 11, 0)
|
||||
|
||||
p.inventory = ActorInventory(p, 0, ActorInventory.CAPACITY_MODE_NO_ENCUMBER)
|
||||
|
||||
p.faction.add(FactionFactory.create("basegame", "factions/FactionSigrid.json"))
|
||||
|
||||
|
||||
|
||||
// Test fill up inventory
|
||||
val blocks = arrayOf(
|
||||
Block.AIR, Block.DIRT, Block.GLASS_CRUDE,
|
||||
Block.GRASS, Block.GRAVEL, Block.ICE_MAGICAL, Block.LANTERN,
|
||||
Block.PLANK_BIRCH, Block.PLANK_BLOODROSE, Block.PLANK_EBONY, Block.PLANK_NORMAL,
|
||||
Block.SANDSTONE, Block.SANDSTONE_BLACK, Block.SANDSTONE_GREEN,
|
||||
Block.SANDSTONE_RED, Block.STONE, Block.STONE_BRICKS,
|
||||
Block.STONE_QUARRIED, Block.STONE_TILE_WHITE, Block.TORCH,
|
||||
Block.DAYLIGHT_CAPACITOR, Block.ICE_FRAGILE,
|
||||
Block.ILLUMINATOR_WHITE, Block.ILLUMINATOR_BLACK, Block.ILLUMINATOR_ORANGE,
|
||||
Block.ILLUMINATOR_GREEN, Block.ILLUMINATOR_CYAN, Block.SUNSTONE
|
||||
)
|
||||
val walls = arrayOf(
|
||||
Block.AIR, Block.DIRT, Block.GLASS_CRUDE,
|
||||
Block.GRASSWALL, Block.ICE_MAGICAL,
|
||||
Block.PLANK_BIRCH, Block.PLANK_BLOODROSE, Block.PLANK_EBONY, Block.PLANK_NORMAL,
|
||||
Block.SANDSTONE, Block.SANDSTONE_BLACK, Block.SANDSTONE_GREEN,
|
||||
Block.SANDSTONE_RED, Block.STONE, Block.STONE_BRICKS,
|
||||
Block.STONE_QUARRIED, Block.STONE_TILE_WHITE
|
||||
)
|
||||
blocks.forEach { p.addItem(it, 999) }
|
||||
walls.forEach { p.addItem(it + 4096, 999) }
|
||||
p.inventory.add(ItemCodex.ITEM_STATIC.first)
|
||||
|
||||
|
||||
|
||||
|
||||
return p
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
|
||||
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2017-02-10.
|
||||
*/
|
||||
object PlayerBuilderTestSubject1 {
|
||||
operator fun invoke(): Player {
|
||||
val p: Player = Player((Terrarum.ingame!! as Ingame).world, GameDate(100, 143)) // random value thrown
|
||||
InjectCreatureRaw(p.actorValue, "basegame", "CreatureHuman.json")
|
||||
|
||||
|
||||
p.actorValue[AVKey.__PLAYER_QUICKSLOTSEL] = 0
|
||||
p.actorValue[AVKey.__ACTION_TIMER] = 0.0
|
||||
p.actorValue[AVKey.ACTION_INTERVAL] = ActorHumanoid.BASE_ACTION_INTERVAL
|
||||
p.actorValue[AVKey.NAME] = "Test Subject 1"
|
||||
|
||||
|
||||
p.makeNewSprite(TextureRegionPack(ModMgr.getGdxFile("basegame", "sprites/npc_template_anim_prototype.tga"), 48, 52))
|
||||
p.sprite!!.delay = 0.2f
|
||||
p.sprite!!.setRowsAndFrames(2, 4)
|
||||
|
||||
p.setHitboxDimension(15, p.actorValue.getAsInt(AVKey.BASEHEIGHT) ?: ActorHumanoid.BASE_HEIGHT, 21, 0)
|
||||
|
||||
p.setPosition(4096.0 * FeaturesDrawer.TILE_SIZE, 300.0 * FeaturesDrawer.TILE_SIZE)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return p
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
import net.torvald.terrarum.itemproperties.GameItem
|
||||
import net.torvald.terrarum.itemproperties.ItemCodex
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-01-15.
|
||||
*/
|
||||
interface Pocketed {
|
||||
|
||||
var inventory: ActorInventory
|
||||
|
||||
/**
|
||||
* Equips an item. If the item is not in the inventory, an error will be thrown.
|
||||
*/
|
||||
fun unequipItem(item: GameItem?) {
|
||||
if (item == null) return
|
||||
|
||||
if (item.equipPosition == GameItem.EquipPosition.NULL)
|
||||
throw Error("Unequipping the item that cannot be equipped in the first place")
|
||||
|
||||
if (!inventory.contains(item)) {
|
||||
//throw Error("Unequipping the item that does not exist in inventory")
|
||||
System.err.println("[Pocketed] Warning -- Unequipping the item that does not exist in inventory")
|
||||
return // just do nothing
|
||||
}
|
||||
|
||||
inventory.itemEquipped[item.equipPosition] = null
|
||||
item.effectOnUnequip(Terrarum.deltaTime)
|
||||
}
|
||||
|
||||
// no need for equipSlot(Int)
|
||||
fun unequipSlot(slot: Int) {
|
||||
if (slot < 0 || slot > GameItem.EquipPosition.INDEX_MAX)
|
||||
throw IllegalArgumentException("Slot index out of range: $slot")
|
||||
|
||||
unequipItem(inventory.itemEquipped[slot])
|
||||
}
|
||||
|
||||
/**
|
||||
* Equips an item. If the item is not in the inventory, adds the item first.
|
||||
*/
|
||||
fun equipItem(item: GameItem) {
|
||||
if (!inventory.contains(item)) {
|
||||
println("[Pocketed] Item does not exist; adding one before equipped")
|
||||
inventory.add(item)
|
||||
}
|
||||
|
||||
if (item.equipPosition >= 0) {
|
||||
inventory.itemEquipped[item.equipPosition] = item
|
||||
item.effectWhenEquipped(Terrarum.deltaTime)
|
||||
}
|
||||
// else do nothing
|
||||
}
|
||||
|
||||
fun equipped(item: GameItem): Boolean {
|
||||
return inventory.itemEquipped[item.equipPosition] == item
|
||||
}
|
||||
|
||||
fun addItem(itemID: Int, count: Int = 1) = inventory.add(ItemCodex[itemID], count)
|
||||
fun addItem(item: GameItem, count: Int = 1) = inventory.add(item, count)
|
||||
fun removeItem(itemID: Int, count: Int = 1) = inventory.remove(ItemCodex[itemID], count)
|
||||
fun removeItem(item: GameItem, count: Int = 1) = inventory.remove(item, count)
|
||||
|
||||
fun hasItem(item: GameItem) = inventory.contains(item.dynamicID)
|
||||
fun hasItem(id: Int) = inventory.contains(id)
|
||||
|
||||
|
||||
fun consumePrimary(item: GameItem) {
|
||||
if (item.primaryUse(Terrarum.deltaTime)) {
|
||||
inventory.consumeItem(this as Actor, item) // consume on successful
|
||||
}
|
||||
}
|
||||
|
||||
fun consumeSecondary(item: GameItem) {
|
||||
if (item.secondaryUse(Terrarum.deltaTime))
|
||||
inventory.consumeItem(this as Actor, item) // consume on successful
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
/**
|
||||
* Projectile marker. Used to kill them when they're far away from the player, instead of making them sleep.
|
||||
*
|
||||
* Created by minjaesong on 2016-09-05.
|
||||
*/
|
||||
interface Projectile {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import org.dyn4j.geometry.Vector2
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-08-29.
|
||||
*/
|
||||
class ProjectileHoming(
|
||||
world: GameWorld,
|
||||
type: Int,
|
||||
fromPoint: Vector2, // projected coord
|
||||
toPoint: Vector2 // arriving coord
|
||||
) : ProjectileSimple(world, type, fromPoint, toPoint) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.point.Point2d
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.gameactors.Hitbox
|
||||
import net.torvald.terrarum.gameactors.Luminous
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import org.dyn4j.geometry.Vector2
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Simplest projectile.
|
||||
*
|
||||
* Created by minjaesong on 2016-08-29.
|
||||
*/
|
||||
|
||||
// TODO simplified, lightweight physics (does not call PhysicsSolver)
|
||||
open class ProjectileSimple(
|
||||
world: GameWorld,
|
||||
private val type: Int,
|
||||
fromPoint: Vector2, // projected coord
|
||||
toPoint: Vector2 // arriving coord
|
||||
) : ActorWithPhysics(world, RenderOrder.MIDTOP), Luminous, Projectile {
|
||||
|
||||
val damage: Int
|
||||
val displayColour: Color
|
||||
/** scalar part of velocity */
|
||||
val speed: Int
|
||||
|
||||
|
||||
override var color: Color
|
||||
get() = (bulletDatabase[type][OFFSET_LUMINOSITY] as Color).cpy()
|
||||
set(value) {
|
||||
}
|
||||
/**
|
||||
* Arguments:
|
||||
*
|
||||
* Hitbox(x-offset, y-offset, width, height)
|
||||
* (Use ArrayList for normal circumstances)
|
||||
*/
|
||||
override val lightBoxList = ArrayList<Hitbox>()
|
||||
|
||||
private val lifetimeMax = 2500
|
||||
private var lifetimeCounter = 0f
|
||||
|
||||
private val posPre: Point2d
|
||||
|
||||
init {
|
||||
setPosition(fromPoint.x, fromPoint.y)
|
||||
posPre = Point2d(fromPoint.x, fromPoint.y)
|
||||
// lightbox sized 8x8 centered to the bullet
|
||||
lightBoxList.add(Hitbox(-4.0, -4.0, 8.0, 8.0))
|
||||
//this.externalForce.set(velocity)
|
||||
|
||||
damage = bulletDatabase[type][OFFSET_DAMAGE] as Int
|
||||
displayColour = bulletDatabase[type][OFFSET_COL] as Color
|
||||
isNoSubjectToGrav = bulletDatabase[type][OFFSET_NOGRAVITY] as Boolean
|
||||
speed = bulletDatabase[type][OFFSET_SPEED] as Int
|
||||
|
||||
setHitboxDimension(2, 2, 0, 0) // should be following sprite's properties if there IS one
|
||||
|
||||
|
||||
externalForce.set((fromPoint to toPoint).setMagnitude(speed.toDouble()))
|
||||
|
||||
|
||||
|
||||
collisionType = COLLISION_KINEMATIC
|
||||
}
|
||||
|
||||
override fun update(delta: Float) {
|
||||
// hit something and despawn
|
||||
lifetimeCounter += delta
|
||||
if (walledTop || walledBottom || walledRight || walledLeft || lifetimeCounter >= lifetimeMax ||
|
||||
// stuck check
|
||||
BlockCodex[(Terrarum.ingame!! as Ingame).world.getTileFromTerrain(feetPosTile[0], feetPosTile[1]) ?: Block.STONE].isSolid
|
||||
) {
|
||||
flagDespawn()
|
||||
}
|
||||
|
||||
posPre.set(centrePosPoint)
|
||||
|
||||
super.update(delta)
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING! ends and begins Batch
|
||||
*/
|
||||
override fun drawBody(batch: SpriteBatch) {
|
||||
val colourTail = displayColour.cpy() // clone a colour
|
||||
colourTail.a = 0.16f
|
||||
|
||||
/*batch.end()
|
||||
Terrarum.inShapeRenderer {
|
||||
// draw trail of solid colour (Terraria style maybe?)
|
||||
it.lineWidth = 2f * Terrarum.ingame!!.screenZoom
|
||||
g.drawGradientLine(
|
||||
hitbox.centeredX.toFloat() * Terrarum.ingame!!.screenZoom,
|
||||
hitbox.centeredY.toFloat() * Terrarum.ingame!!.screenZoom,
|
||||
displayColour,
|
||||
posPre.x.toFloat() * Terrarum.ingame!!.screenZoom,
|
||||
posPre.y.toFloat() * Terrarum.ingame!!.screenZoom,
|
||||
colourTail
|
||||
)
|
||||
}
|
||||
batch.begin()*/
|
||||
}
|
||||
|
||||
override fun drawGlow(batch: SpriteBatch) = drawBody(batch)
|
||||
|
||||
companion object {
|
||||
val OFFSET_DAMAGE = 0
|
||||
val OFFSET_COL = 1 // Color or SpriteAnimation
|
||||
val OFFSET_NOGRAVITY = 2
|
||||
val OFFSET_SPEED = 3
|
||||
val OFFSET_LUMINOSITY = 4
|
||||
val bulletDatabase = arrayOf(
|
||||
// damage, display colour, no gravity, speed
|
||||
arrayOf(7, Color(0xFF5429_FF.toInt()), true, 40, 32),
|
||||
arrayOf(8, Color(0xFF5429_FF.toInt()), true, 20, 0)
|
||||
// ...
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.Pixmap
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2017-01-07.
|
||||
*/
|
||||
class TapestryObject(world: GameWorld, pixmap: Pixmap, val artName: String, val artAuthor: String) : FixtureBase(world, physics = false) {
|
||||
|
||||
// physics = false only speeds up for ~2 frames with 50 tapestries
|
||||
|
||||
init {
|
||||
val texture = Texture(pixmap)
|
||||
pixmap.dispose()
|
||||
texture.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
|
||||
val texturePack = TextureRegionPack(texture, texture.width, texture.height)
|
||||
|
||||
makeNewSprite(texturePack)
|
||||
setHitboxDimension(texture.width, texture.height, 0, 0)
|
||||
setPosition(Terrarum.mouseX, Terrarum.mouseY)
|
||||
// you CAN'T destroy the image
|
||||
}
|
||||
|
||||
override fun update(delta: Float) {
|
||||
super.update(delta)
|
||||
}
|
||||
|
||||
override fun drawBody(batch: SpriteBatch) {
|
||||
super.drawBody(batch)
|
||||
}
|
||||
|
||||
override var tooltipText: String? = "$artName\n$artAuthor"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import net.torvald.terrarum.Terrarum
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-05-25.
|
||||
*/
|
||||
class ThreadActorUpdate(val startIndex: Int, val endIndex: Int) : Runnable {
|
||||
override fun run() {
|
||||
for (i in startIndex..endIndex) {
|
||||
val it = Terrarum.ingame!!.actorContainer[i]
|
||||
it.update(Terrarum.deltaTime)
|
||||
|
||||
if (it is Pocketed) {
|
||||
it.inventory.forEach { inventoryEntry ->
|
||||
inventoryEntry.item.effectWhileInPocket(Terrarum.deltaTime)
|
||||
if (it.equipped(inventoryEntry.item)) {
|
||||
inventoryEntry.item.effectWhenEquipped(Terrarum.deltaTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import net.torvald.terrarum.gameactors.Hitbox
|
||||
import net.torvald.terrarum.gameactors.Luminous
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-04-26.
|
||||
*/
|
||||
class WeaponSwung(world: GameWorld, val itemID: Int) : ActorWithPhysics(world, RenderOrder.MIDTOP), Luminous {
|
||||
// just let the solver use AABB; it's cheap but works just enough
|
||||
|
||||
/**
|
||||
* Recommended implementation:
|
||||
*
|
||||
override var color: Int
|
||||
get() = actorValue.getAsInt(AVKey.LUMINOSITY) ?: 0
|
||||
set(value) {
|
||||
actorValue[AVKey.LUMINOSITY] = value
|
||||
}
|
||||
*/
|
||||
override var color: Color
|
||||
get() = throw UnsupportedOperationException()
|
||||
set(value) {
|
||||
}
|
||||
/**
|
||||
* Arguments:
|
||||
*
|
||||
* Hitbox(x-offset, y-offset, width, height)
|
||||
* (Use ArrayList for normal circumstances)
|
||||
*/
|
||||
override val lightBoxList: List<Hitbox>
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
init {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors.ai
|
||||
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.HumanoidNPC
|
||||
import net.torvald.terrarum.Second
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
import net.torvald.terrarum.gameactors.ai.ActorAI
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
|
||||
/**
|
||||
* Slime's stupid AI but can adjust his jump power to smack you as fast as possible
|
||||
* by achieving "allostasis".
|
||||
*
|
||||
* Created by minjaesong on 2017-12-10.
|
||||
*/
|
||||
class SmarterSlimes : ActorAI {
|
||||
|
||||
val memoryCells = IntArray(12, { 0 })
|
||||
// index 0: most recent memory
|
||||
// intentionally making it stupid by using less precise INT
|
||||
// also we're not discrimination different enemies, making it further dumb
|
||||
// stores "overshoot" amount (learn target) of x position
|
||||
|
||||
var maxJumpDist: Double = -1.0
|
||||
|
||||
var cooltime: Second = 5f
|
||||
|
||||
override fun update(actor: Actor, delta: Float) {
|
||||
val actor = actor as HumanoidNPC
|
||||
|
||||
|
||||
// sensor: compare(my X pos, nearest enemy's X pos)
|
||||
maxJumpDist = actor.avSpeedCap * actor.jumpAirTime // speed * air_time
|
||||
// (to be precise, we need simulation just like jumpAirTime, but oh well; we like it LINEAR)
|
||||
|
||||
|
||||
// TEST: just target player
|
||||
val playerXPos = (Terrarum.ingame!! as Ingame).player.centrePosPoint.x
|
||||
val thisXPos = actor.centrePosPoint.x
|
||||
val xDiff = thisXPos - playerXPos
|
||||
|
||||
|
||||
|
||||
// extrapolate from memories:
|
||||
// otherwise linear extp. except the slope is d of 0th and 2nd point
|
||||
|
||||
|
||||
|
||||
if (xDiff > 0) {
|
||||
actor.moveLeft()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors.physicssolver
|
||||
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.ActorWithPhysics
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-04-22.
|
||||
*/
|
||||
object CollisionSolver {
|
||||
|
||||
private const val STARTPOINT = 1
|
||||
private const val ENDPOINT = 2
|
||||
|
||||
private const val COLL_LIST_SIZE = 256
|
||||
private const val COLL_CANDIDATES_SIZE = 128
|
||||
private const val COLL_FINAL_CANDIDATES_SIZE = 16
|
||||
|
||||
private val collListX = ArrayList<CollisionMarkings>(COLL_LIST_SIZE)
|
||||
private val collListY = ArrayList<CollisionMarkings>(COLL_LIST_SIZE)
|
||||
|
||||
private val collCandidateX = ArrayList<Pair<ActorWithPhysics, ActorWithPhysics>>(COLL_CANDIDATES_SIZE)
|
||||
private val collCandidateY = ArrayList<Pair<ActorWithPhysics, ActorWithPhysics>>(COLL_CANDIDATES_SIZE)
|
||||
private var collCandidates = ArrayList<Pair<ActorWithPhysics, ActorWithPhysics>>(COLL_FINAL_CANDIDATES_SIZE)
|
||||
|
||||
private val collCandidateStack = Stack<CollisionMarkings>()
|
||||
|
||||
/**
|
||||
* to see what's going on here, visit
|
||||
* [this link](https://www.toptal.com/game/video-game-physics-part-ii-collision-detection-for-solid-objects)
|
||||
*/
|
||||
fun process() {
|
||||
// TODO threading X and Y
|
||||
// clean up before we go
|
||||
collListX.clear()
|
||||
collListY.clear()
|
||||
collCandidateX.clear()
|
||||
collCandidateY.clear()
|
||||
|
||||
// mark list x
|
||||
(Terrarum.ingame!! as Ingame).actorContainer.forEach { it ->
|
||||
if (it is ActorWithPhysics) {
|
||||
collListX.add(CollisionMarkings(it.hitbox.hitboxStart.x, STARTPOINT, it))
|
||||
collListX.add(CollisionMarkings(it.hitbox.hitboxEnd.x, ENDPOINT, it))
|
||||
}
|
||||
}
|
||||
// sort list x
|
||||
collListX.sortBy { it.pos }
|
||||
|
||||
// set candidateX
|
||||
collListX.forEach {
|
||||
if (it.kind == STARTPOINT) {
|
||||
collCandidateStack.push(it)
|
||||
}
|
||||
else if (it.kind == ENDPOINT) {
|
||||
val mark_this = it
|
||||
val mark_other = collCandidateStack.pop()
|
||||
// make sure actor with lower ID comes first
|
||||
val collCandidate = if (mark_this.actor < mark_other.actor)
|
||||
Pair(mark_this.actor, mark_other.actor)
|
||||
else
|
||||
Pair(mark_other.actor, mark_this.actor)
|
||||
|
||||
// filter out Pair(E, E); Pair(A, B) if Pair(B, A) exists
|
||||
if (mark_this.actor != mark_other.actor) {
|
||||
collCandidateX.add(collCandidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collCandidateStack.clear()
|
||||
|
||||
// mark list y
|
||||
(Terrarum.ingame!! as Ingame).actorContainer.forEach { it ->
|
||||
if (it is ActorWithPhysics) {
|
||||
collListY.add(CollisionMarkings(it.hitbox.hitboxStart.y, STARTPOINT, it))
|
||||
collListY.add(CollisionMarkings(it.hitbox.hitboxEnd.y, ENDPOINT, it))
|
||||
}
|
||||
}
|
||||
// sort list y
|
||||
collListY.sortBy { it.pos }
|
||||
|
||||
// set candidateY
|
||||
collListY.forEach {
|
||||
if (it.kind == STARTPOINT) {
|
||||
collCandidateStack.push(it)
|
||||
}
|
||||
else if (it.kind == ENDPOINT) {
|
||||
val mark_this = it
|
||||
val mark_other = collCandidateStack.pop()
|
||||
val collCandidate: Pair<ActorWithPhysics, ActorWithPhysics>
|
||||
// make sure actor with lower ID comes first
|
||||
if (mark_this.actor < mark_other.actor)
|
||||
collCandidate = Pair(mark_this.actor, mark_other.actor)
|
||||
else
|
||||
collCandidate = Pair(mark_other.actor, mark_this.actor)
|
||||
|
||||
// filter out Pair(E, E); Pair(A, B) if Pair(B, A) exists
|
||||
if (mark_this.actor != mark_other.actor) {
|
||||
collCandidateY.add(collCandidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
// look for overlaps in candidate X/Y and put them into collCandidates
|
||||
// overlapping in X and Y means they are actually overlapping physically
|
||||
collCandidateY.retainAll(collCandidateX) // list Y will have intersection of X and Y now
|
||||
collCandidates = collCandidateY // renaming. X and Y won't be used anyway.
|
||||
|
||||
//collCandidates.forEach { println(it) }
|
||||
//println("-----------------------")
|
||||
|
||||
// solve collision for actors in collCandidates
|
||||
collCandidates.forEach { solveCollision(it.first, it.second) }
|
||||
}
|
||||
|
||||
private fun pairEqv(a: Pair<Any?, Any?>, b: Pair<Any?, Any?>) =
|
||||
(a.first == b.first && a.second == b.second) ||
|
||||
(a.first == b.second && a.second == b.first)
|
||||
|
||||
/** Mimics java's original behaviour, with user-defined equals function */
|
||||
fun ArrayList<Any?>.containsByFunc(other: Any?, equalsFun: (a: Any?, b: Any?) -> Boolean): Boolean {
|
||||
fun indexOfEqFn(arrayList: ArrayList<Any?>, o: Any?): Int {
|
||||
if (o == null) {
|
||||
for (i in 0..size - 1)
|
||||
if (arrayList[i] == null)
|
||||
return i
|
||||
}
|
||||
else {
|
||||
for (i in 0..size - 1)
|
||||
if (equalsFun(o, arrayList[i]))
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
return indexOfEqFn(this, other) >= 0
|
||||
}
|
||||
|
||||
private fun solveCollision(a: ActorWithPhysics, b: ActorWithPhysics) {
|
||||
// some of the Pair(a, b) are either duplicates or erroneously reported.
|
||||
// e.g. (A, B), (B, C) and then (A, C);
|
||||
// in some situation (A, C) will not making any contact with each other
|
||||
// we are going to filter them
|
||||
if (a isCollidingWith b) {
|
||||
// notify collision, but not solve it yet
|
||||
|
||||
//println("Collision: $a <-> $b")
|
||||
// FIXME does work but has duplication
|
||||
|
||||
// if they actually makes collision (e.g. player vs ball), solve it
|
||||
if (a makesCollisionWith b) {
|
||||
val a_moveDelta = a.externalForce + a.controllerMoveDelta
|
||||
val b_moveDelta = b.externalForce + b.controllerMoveDelta
|
||||
|
||||
val ux_1 = a_moveDelta.x
|
||||
val ux_2 = b_moveDelta.x
|
||||
val uy_1 = a_moveDelta.y
|
||||
val uy_2 = b_moveDelta.y
|
||||
val m1 = a.mass
|
||||
val m2 = b.mass
|
||||
|
||||
val vx_1 = (ux_2 * (m1 - m2) + 2 * m2 * ux_2) / (m1 + m2)
|
||||
val vx_2 = (ux_2 * (m2 - m1) + 2 * m1 * ux_1) / (m1 + m2)
|
||||
val vy_1 = (uy_2 * (m1 - m2) + 2 * m2 * uy_2) / (m1 + m2)
|
||||
val vy_2 = (uy_2 * (m2 - m1) + 2 * m1 * uy_1) / (m1 + m2)
|
||||
|
||||
/*a.veloX = vx_1
|
||||
a.veloY = vy_1
|
||||
b.veloX = vx_2
|
||||
b.veloY = vy_2*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private infix fun ActorWithPhysics.makesCollisionWith(other: ActorWithPhysics) =
|
||||
this.collisionType != ActorWithPhysics.COLLISION_NOCOLLIDE &&
|
||||
other.collisionType != ActorWithPhysics.COLLISION_NOCOLLIDE
|
||||
|
||||
private infix fun ActorWithPhysics.isCollidingWith(other: ActorWithPhysics): Boolean {
|
||||
val ax = this.hitbox.centeredX
|
||||
val ay = this.hitbox.centeredY
|
||||
val bx = other.hitbox.centeredX
|
||||
val by = other.hitbox.centeredY
|
||||
|
||||
// will refer 'actor_dist_t' as 't' afterward
|
||||
val actor_dist_t_sqr = ((ay - by).sqr() + (ax - bx).sqr()) // no sqrt; 'power' is slower than 'times'
|
||||
val dist_x = (ax - bx).abs() // 'tx'
|
||||
val dist_y = (ay - by).abs() // 'ty'
|
||||
val tangent = dist_y / dist_x
|
||||
|
||||
var t_ax: Double; var t_ay: Double
|
||||
if (dist_x > dist_y) {
|
||||
t_ax = this.hitbox.width / 2
|
||||
t_ay = t_ax * tangent
|
||||
}
|
||||
else {
|
||||
t_ay = this.hitbox.height / 2
|
||||
t_ax = t_ay * tangent
|
||||
}
|
||||
|
||||
return (t_ax.sqr() + t_ay.sqr()) < actor_dist_t_sqr
|
||||
}
|
||||
|
||||
fun Double.abs() = if (this < 0) -this else this
|
||||
fun Double.sqr() = this * this
|
||||
|
||||
data class CollisionMarkings(
|
||||
val pos: Double,
|
||||
val kind: Int,
|
||||
val actor: ActorWithPhysics
|
||||
)
|
||||
|
||||
/**
|
||||
* === Some useful physics knowledge ===
|
||||
*
|
||||
* * Momentum = mass × Velocity (p = mv, conserved)
|
||||
*
|
||||
* * Force = mass × acceleration (f = ma, conserved)
|
||||
*
|
||||
* * F_AB = -F_BA (Lex Tertia, does NOT apply to fictitious force like centrifugal)
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors.physicssolver
|
||||
|
||||
/**
|
||||
* multithreaded version of CollisionSolver#solveCollision
|
||||
* Created by minjaesong on 2016-04-26.
|
||||
*/
|
||||
internal class SolveByUnit : Runnable {
|
||||
override fun run() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors.physicssolver
|
||||
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.ActorWithPhysics
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-05-01.
|
||||
*/
|
||||
object VelocitySolver {
|
||||
|
||||
fun process() {
|
||||
|
||||
}
|
||||
|
||||
private fun applyGravity(actor: ActorWithPhysics) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user