still wip modularisation, game somehow boots

This commit is contained in:
minjaesong
2018-06-21 17:33:22 +09:00
parent 6bbfd5d167
commit 8daf0a2c38
266 changed files with 2409 additions and 1122 deletions

View 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"
}

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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")
}
}

View File

@@ -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
}
}
}

View File

@@ -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()
}

View File

@@ -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())
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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
}
}

View 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)
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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 {
}

View File

@@ -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) {
}

View File

@@ -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)
// ...
)
}
}

View File

@@ -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"
}

View File

@@ -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)
}
}
}
}
}
}

View File

@@ -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 {
}
}

View File

@@ -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()
}
}
}

View File

@@ -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)
*/
}

View File

@@ -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()
}
}

View File

@@ -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) {
}
}