diff --git a/src/net/torvald/terrarum/gameactors/ActorWithBody.kt b/src/net/torvald/terrarum/gameactors/ActorWithBody.kt index c1cb17805..e5ddb8d9c 100644 --- a/src/net/torvald/terrarum/gameactors/ActorWithBody.kt +++ b/src/net/torvald/terrarum/gameactors/ActorWithBody.kt @@ -23,6 +23,7 @@ import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer import net.torvald.terrarum.modulebasegame.gameactors.Pocketed +import net.torvald.terrarum.modulebasegame.ui.UIItemInventoryCellCommonRes.tooltipShowing import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.worlddrawer.CreateTileAtlas import net.torvald.terrarum.worlddrawer.WorldCamera @@ -97,6 +98,8 @@ open class ActorWithBody : Actor { val mouseUp: Boolean get() = hitbox.containsPoint((world?.width ?: 0) * TILE_SIZED, Terrarum.mouseX, Terrarum.mouseY) + @Transient private val tooltipHash = System.nanoTime() + var hitboxTranslateX: Int = 0// relative to spritePosX protected set var hitboxTranslateY: Int = 0// relative to spritePosY @@ -669,11 +672,15 @@ open class ActorWithBody : Actor { feetPosTile.set(hIntTilewiseHitbox.centeredX.floorToInt(), hIntTilewiseHitbox.endY.floorToInt()) - if (mouseUp && this.tooltipText != null) INGAME.setTooltipMessage(this.tooltipText) + if (mouseUp && tooltipText != null && tooltipShowing[tooltipHash] != true) { + INGAME.setTooltipMessage(tooltipText) + tooltipShowing[tooltipHash] = true + } } - // make sure tooltip to disable even when the actor's update is paused at unfortunate time - if (!mouseUp && INGAME.getTooltipMessage() == this.tooltipText) INGAME.setTooltipMessage(null) + if (tooltipText == null || !mouseUp) { + tooltipShowing[tooltipHash] = false + } // isStationary = (hitbox - oldHitbox).magnitudeSquared < PHYS_EPSILON_VELO isStationary = isCloseEnough(hitbox.startX, oldHitbox.startX) && // this is supposed to be more accurate, idk diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/Electric.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/Electric.kt new file mode 100644 index 000000000..4bf29afe3 --- /dev/null +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/Electric.kt @@ -0,0 +1,200 @@ +package net.torvald.terrarum.modulebasegame.gameactors + +import net.torvald.terrarum.* +import net.torvald.terrarum.gameactors.ActorID +import net.torvald.terrarum.gameactors.PhysProperties +import net.torvald.terrarum.ui.UICanvas +import org.dyn4j.geometry.Vector2 +import java.util.ArrayList + +typealias WireEmissionType = String + +/** + * Created by minjaesong on 2021-08-10. + */ +open class Electric : FixtureBase { + + protected constructor() : super() { + oldSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() } + newSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() } + } + + /** + * Making the sprite: do not address the CommonResourcePool directly; just do it like this snippet: + * + * ```makeNewSprite(FixtureBase.getSpritesheet("basegame", "sprites/fixtures/tiki_torch.tga", 16, 32))``` + */ + constructor( + blockBox0: BlockBox, + blockBoxProps: BlockBoxProps = BlockBoxProps(0), + renderOrder: RenderOrder = RenderOrder.MIDDLE, + nameFun: () -> String, + mainUI: UICanvas? = null, + inventory: FixtureInventory? = null, + id: ActorID? = null + ) : super(renderOrder, PhysProperties.IMMOBILE(), id) { + blockBox = blockBox0 + setHitboxDimension(TerrarumAppConfiguration.TILE_SIZE * blockBox.width, TerrarumAppConfiguration.TILE_SIZE * blockBox.height, 0, 0) + this.blockBoxProps = blockBoxProps + this.renderOrder = renderOrder + this.nameFun = nameFun + this.mainUI = mainUI + this.inventory = inventory + + if (mainUI != null) + App.disposables.add(mainUI) + + oldSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() } + newSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() } + } + + companion object { + const val ELECTRIC_THRESHOLD_HIGH = 0.9 + const val ELECTRIC_THRESHOLD_LOW = 0.1 + const val ELECTRIC_THRESHOLD_EDGE_DELTA = 0.7 + } + + fun getWireEmitterAt(blockBoxIndex: BlockBoxIndex) = this.wireEmitterTypes[blockBoxIndex] + fun getWireEmitterAt(point: Point2i) = this.wireEmitterTypes[pointToBlockBoxIndex(point)] + fun getWireEmitterAt(x: Int, y: Int) = this.wireEmitterTypes[pointToBlockBoxIndex(x, y)] + fun getWireSinkAt(blockBoxIndex: BlockBoxIndex) = this.wireSinkTypes[blockBoxIndex] + fun getWireSinkAt(point: Point2i) = this.wireSinkTypes[pointToBlockBoxIndex(point)] + fun getWireSinkAt(x: Int, y: Int) = this.wireSinkTypes[pointToBlockBoxIndex(x, y)] + + fun setWireEmitterAt(x: Int, y: Int, type: WireEmissionType) { wireEmitterTypes[pointToBlockBoxIndex(x, y)] = type } + fun setWireSinkAt(x: Int, y: Int, type: WireEmissionType) { wireSinkTypes[pointToBlockBoxIndex(x, y)] = type } + fun setWireEmissionAt(x: Int, y: Int, emission: Vector2) { wireEmission[pointToBlockBoxIndex(x, y)] = emission } + fun setWireConsumptionAt(x: Int, y: Int, consumption: Vector2) { wireConsumption[pointToBlockBoxIndex(x, y)] = consumption } + + fun clearStatus() { + wireSinkTypes.clear() + wireEmitterTypes.clear() + wireEmission.clear() + wireConsumption.clear() + } + + // these are characteristic properties of the fixture (they have constant value) so must not be serialised + @Transient val wireEmitterTypes: HashMap = HashMap() + @Transient val wireSinkTypes: HashMap = HashMap() + + val wireEmission: HashMap = HashMap() + val wireConsumption: HashMap = HashMap() + + // these are NOT constant so they ARE serialised. Type: Map Charge (Double> + // Use case: signal buffer (sinkType=digital_bit), battery (sinkType=electricity), etc. + val chargeStored: HashMap = HashMap() + + private val newStates = HashMap() + + /** Triggered when 'digital_bit' rises from low to high. Edge detection only considers the real component (labeled as 'x') of the vector */ + open fun onRisingEdge(readFrom: BlockBoxIndex) {} + /** Triggered when 'digital_bit' rises from high to low. Edge detection only considers the real component (labeled as 'x') of the vector */ + open fun onFallingEdge(readFrom: BlockBoxIndex) {} + /** Triggered when 'digital_bit' is held high. This function WILL NOT be triggered simultaneously with the rising edge. Level detection only considers the real component (labeled as 'x') of the vector */ + //open fun onSignalHigh(readFrom: BlockBoxIndex) {} + /** Triggered when 'digital_bit' is held low. This function WILL NOT be triggered simultaneously with the falling edge. Level detection only considers the real component (labeled as 'x') of the vector */ + //open fun onSignalLow(readFrom: BlockBoxIndex) {} + + open fun updateSignal() {} + + fun getWireStateAt(offsetX: Int, offsetY: Int, sinkType: WireEmissionType): Vector2 { + val index = pointToBlockBoxIndex(offsetX, offsetY) + val wx = offsetX + intTilewiseHitbox.startX.toInt() + val wy = offsetY + intTilewiseHitbox.startY.toInt() + + return WireCodex.getAllWiresThatAccepts(sinkType).fold(Vector2()) { acc, (id, _) -> + INGAME.world.getWireEmitStateOf(wx, wy, id).let { + Vector2(acc.x + (it?.x ?: 0.0), acc.y + (it?.y ?: 0.0)) + } + } + } + + fun getWireEmissionAt(offsetX: Int, offsetY: Int): Vector2 { + return wireEmission[pointToBlockBoxIndex(offsetX, offsetY)] ?: Vector2() + } + + /** + * returns true if at least one of following condition is `true` + * - `getWireStateAt(x, y, "digital_bit").x` is equal to or greater than `ELECTIC_THRESHOLD_HIGH` + * - `getWireEmissionAt(x, y).x` is equal to or greater than `ELECTIC_THRESHOLD_HIGH` + * + * This function does NOT check if the given port receives/emits `digital_bit` signal; if not, the result is undefined. + */ + fun isSignalHigh(offsetX: Int, offsetY: Int) = + getWireStateAt(offsetX, offsetY, "digital_bit").x >= ELECTRIC_THRESHOLD_HIGH || + getWireEmissionAt(offsetX, offsetY).x >= ELECTRIC_THRESHOLD_HIGH + + /** + * returns true if at least one of following condition is `true` + * - `getWireStateAt(x, y, "digital_bit").x` is equal to or lesser than `ELECTRIC_THRESHOLD_LOW` + * - `getWireEmissionAt(x, y).x` is equal to or lesser than `ELECTRIC_THRESHOLD_LOW` + * + * This function does NOT check if the given port receives/emits `digital_bit` signal; if not, the result is undefined. + */ + fun isSignalLow(offsetX: Int, offsetY: Int) = + getWireStateAt(offsetX, offsetY, "digital_bit").x <= ELECTRIC_THRESHOLD_LOW || + getWireEmissionAt(offsetX, offsetY).x <= ELECTRIC_THRESHOLD_LOW + + private val oldSinkStatus: Array + private val newSinkStatus: Array + + open fun updateOnWireGraphTraversal(offsetX: Int, offsetY: Int, sinkType: WireEmissionType) { + val index = pointToBlockBoxIndex(offsetX, offsetY) + val wx = offsetX + intTilewiseHitbox.startX.toInt() + val wy = offsetY + intTilewiseHitbox.startY.toInt() + + val new2 = WireCodex.getAllWiresThatAccepts(wireSinkTypes[index] ?: "").fold(Vector2()) { acc, (id, _) -> + INGAME.world.getWireEmitStateOf(wx, wy, id).let { + Vector2(acc.x + (it?.x ?: 0.0), acc.y + (it?.y ?: 0.0)) + } + } + + + oldSinkStatus[index].set(new2) + } + + /** + * Refrain from updating signal output from this function: there will be 1 extra tick delay if you do so + */ + override fun updateImpl(delta: Float) { + super.updateImpl(delta) + + val risingEdgeIndices = ArrayList() + val fallingEdgeIndices = ArrayList() + + for (y in 0 until blockBox.height) { + for (x in 0 until blockBox.width) { + // get indices of "rising edges" + // get indices of "falling edges" + + val wx = x + intTilewiseHitbox.startX.toInt() + val wy = y + intTilewiseHitbox.startY.toInt() + val new = WireCodex.getAllWiresThatAccepts(getWireSinkAt(x, y) ?: "").fold(Vector2()) { acc, (id, _) -> + INGAME.world.getWireEmitStateOf(wx, wy, id).let { + Vector2(acc.x + (it?.x ?: 0.0), acc.y + (it?.y ?: 0.0)) + } + } + val index = pointToBlockBoxIndex(x, y) + + if (new.x - oldSinkStatus[index].x >= ELECTRIC_THRESHOLD_EDGE_DELTA && new.x >= ELECTRIC_THRESHOLD_HIGH) + risingEdgeIndices.add(index) + else if (oldSinkStatus[index].x - new.x >= ELECTRIC_THRESHOLD_EDGE_DELTA && new.x <= ELECTRIC_THRESHOLD_LOW) + fallingEdgeIndices.add(index) + + + oldSinkStatus[index].set(new) + } + } + + + risingEdgeIndices.forEach { onRisingEdge(it) } + fallingEdgeIndices.forEach { onFallingEdge(it) } + updateSignal() + + + + /*oldSinkStatus.indices.forEach { index -> + oldSinkStatus[index].set(new) + }*/ + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureBase.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureBase.kt index 9a2786374..7becb3c9b 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureBase.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureBase.kt @@ -11,200 +11,10 @@ import net.torvald.terrarum.gameworld.fmod import net.torvald.terrarum.modulebasegame.gameitems.PickaxeCore import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack -import org.dyn4j.geometry.Vector2 import java.util.* -import kotlin.collections.HashMap import kotlin.math.roundToInt typealias BlockBoxIndex = Int -typealias WireEmissionType = String - -open class Electric : FixtureBase { - - protected constructor() : super() { - oldSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() } - newSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() } - } - - /** - * Making the sprite: do not address the CommonResourcePool directly; just do it like this snippet: - * - * ```makeNewSprite(FixtureBase.getSpritesheet("basegame", "sprites/fixtures/tiki_torch.tga", 16, 32))``` - */ - constructor( - blockBox0: BlockBox, - blockBoxProps: BlockBoxProps = BlockBoxProps(0), - renderOrder: RenderOrder = RenderOrder.MIDDLE, - nameFun: () -> String, - mainUI: UICanvas? = null, - inventory: FixtureInventory? = null, - id: ActorID? = null - ) : super(renderOrder, PhysProperties.IMMOBILE(), id) { - blockBox = blockBox0 - setHitboxDimension(TILE_SIZE * blockBox.width, TILE_SIZE * blockBox.height, 0, 0) - this.blockBoxProps = blockBoxProps - this.renderOrder = renderOrder - this.nameFun = nameFun - this.mainUI = mainUI - this.inventory = inventory - - if (mainUI != null) - App.disposables.add(mainUI) - - oldSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() } - newSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() } - } - - companion object { - const val ELECTRIC_THRESHOLD_HIGH = 0.9 - const val ELECTRIC_THRESHOLD_LOW = 0.1 - const val ELECTRIC_THRESHOLD_EDGE_DELTA = 0.7 - } - - fun getWireEmitterAt(blockBoxIndex: BlockBoxIndex) = this.wireEmitterTypes[blockBoxIndex] - fun getWireEmitterAt(point: Point2i) = this.wireEmitterTypes[pointToBlockBoxIndex(point)] - fun getWireEmitterAt(x: Int, y: Int) = this.wireEmitterTypes[pointToBlockBoxIndex(x, y)] - fun getWireSinkAt(blockBoxIndex: BlockBoxIndex) = this.wireSinkTypes[blockBoxIndex] - fun getWireSinkAt(point: Point2i) = this.wireSinkTypes[pointToBlockBoxIndex(point)] - fun getWireSinkAt(x: Int, y: Int) = this.wireSinkTypes[pointToBlockBoxIndex(x, y)] - - fun setWireEmitterAt(x: Int, y: Int, type: WireEmissionType) { wireEmitterTypes[pointToBlockBoxIndex(x, y)] = type } - fun setWireSinkAt(x: Int, y: Int, type: WireEmissionType) { wireSinkTypes[pointToBlockBoxIndex(x, y)] = type } - fun setWireEmissionAt(x: Int, y: Int, emission: Vector2) { wireEmission[pointToBlockBoxIndex(x, y)] = emission } - fun setWireConsumptionAt(x: Int, y: Int, consumption: Vector2) { wireConsumption[pointToBlockBoxIndex(x, y)] = consumption } - - fun clearStatus() { - wireSinkTypes.clear() - wireEmitterTypes.clear() - wireEmission.clear() - wireConsumption.clear() - } - - // these are characteristic properties of the fixture (they have constant value) so must not be serialised - @Transient val wireEmitterTypes: HashMap = HashMap() - @Transient val wireSinkTypes: HashMap = HashMap() - - val wireEmission: HashMap = HashMap() - val wireConsumption: HashMap = HashMap() - - // these are NOT constant so they ARE serialised. Type: Map Charge (Double> - // Use case: signal buffer (sinkType=digital_bit), battery (sinkType=electricity), etc. - val chargeStored: HashMap = HashMap() - - private val newStates = HashMap() - - /** Triggered when 'digital_bit' rises from low to high. Edge detection only considers the real component (labeled as 'x') of the vector */ - open fun onRisingEdge(readFrom: BlockBoxIndex) {} - /** Triggered when 'digital_bit' rises from high to low. Edge detection only considers the real component (labeled as 'x') of the vector */ - open fun onFallingEdge(readFrom: BlockBoxIndex) {} - /** Triggered when 'digital_bit' is held high. This function WILL NOT be triggered simultaneously with the rising edge. Level detection only considers the real component (labeled as 'x') of the vector */ - //open fun onSignalHigh(readFrom: BlockBoxIndex) {} - /** Triggered when 'digital_bit' is held low. This function WILL NOT be triggered simultaneously with the falling edge. Level detection only considers the real component (labeled as 'x') of the vector */ - //open fun onSignalLow(readFrom: BlockBoxIndex) {} - - open fun updateSignal() {} - - fun getWireStateAt(offsetX: Int, offsetY: Int, sinkType: WireEmissionType): Vector2 { - val index = pointToBlockBoxIndex(offsetX, offsetY) - val wx = offsetX + intTilewiseHitbox.startX.toInt() - val wy = offsetY + intTilewiseHitbox.startY.toInt() - - return WireCodex.getAllWiresThatAccepts(sinkType).fold(Vector2()) { acc, (id, _) -> - INGAME.world.getWireEmitStateOf(wx, wy, id).let { - Vector2(acc.x + (it?.x ?: 0.0), acc.y + (it?.y ?: 0.0)) - } - } - } - - fun getWireEmissionAt(offsetX: Int, offsetY: Int): Vector2 { - return wireEmission[pointToBlockBoxIndex(offsetX, offsetY)] ?: Vector2() - } - - /** - * returns true if at least one of following condition is `true` - * - `getWireStateAt(x, y, "digital_bit").x` is equal to or greater than `ELECTIC_THRESHOLD_HIGH` - * - `getWireEmissionAt(x, y).x` is equal to or greater than `ELECTIC_THRESHOLD_HIGH` - * - * This function does NOT check if the given port receives/emits `digital_bit` signal; if not, the result is undefined. - */ - fun isSignalHigh(offsetX: Int, offsetY: Int) = - getWireStateAt(offsetX, offsetY, "digital_bit").x >= ELECTRIC_THRESHOLD_HIGH || - getWireEmissionAt(offsetX, offsetY).x >= ELECTRIC_THRESHOLD_HIGH - - /** - * returns true if at least one of following condition is `true` - * - `getWireStateAt(x, y, "digital_bit").x` is equal to or lesser than `ELECTRIC_THRESHOLD_LOW` - * - `getWireEmissionAt(x, y).x` is equal to or lesser than `ELECTRIC_THRESHOLD_LOW` - * - * This function does NOT check if the given port receives/emits `digital_bit` signal; if not, the result is undefined. - */ - fun isSignalLow(offsetX: Int, offsetY: Int) = - getWireStateAt(offsetX, offsetY, "digital_bit").x <= ELECTRIC_THRESHOLD_LOW || - getWireEmissionAt(offsetX, offsetY).x <= ELECTRIC_THRESHOLD_LOW - - private val oldSinkStatus: Array - private val newSinkStatus: Array - - open fun updateOnWireGraphTraversal(offsetX: Int, offsetY: Int, sinkType: WireEmissionType) { - val index = pointToBlockBoxIndex(offsetX, offsetY) - val wx = offsetX + intTilewiseHitbox.startX.toInt() - val wy = offsetY + intTilewiseHitbox.startY.toInt() - - val new2 = WireCodex.getAllWiresThatAccepts(wireSinkTypes[index] ?: "").fold(Vector2()) { acc, (id, _) -> - INGAME.world.getWireEmitStateOf(wx, wy, id).let { - Vector2(acc.x + (it?.x ?: 0.0), acc.y + (it?.y ?: 0.0)) - } - } - - - oldSinkStatus[index].set(new2) - } - - /** - * Refrain from updating signal output from this function: there will be 1 extra tick delay if you do so - */ - override fun updateImpl(delta: Float) { - super.updateImpl(delta) - - val risingEdgeIndices = ArrayList() - val fallingEdgeIndices = ArrayList() - - for (y in 0 until blockBox.height) { - for (x in 0 until blockBox.width) { - // get indices of "rising edges" - // get indices of "falling edges" - - val wx = x + intTilewiseHitbox.startX.toInt() - val wy = y + intTilewiseHitbox.startY.toInt() - val new = WireCodex.getAllWiresThatAccepts(getWireSinkAt(x, y) ?: "").fold(Vector2()) { acc, (id, _) -> - INGAME.world.getWireEmitStateOf(wx, wy, id).let { - Vector2(acc.x + (it?.x ?: 0.0), acc.y + (it?.y ?: 0.0)) - } - } - val index = pointToBlockBoxIndex(x, y) - - if (new.x - oldSinkStatus[index].x >= ELECTRIC_THRESHOLD_EDGE_DELTA && new.x >= ELECTRIC_THRESHOLD_HIGH) - risingEdgeIndices.add(index) - else if (oldSinkStatus[index].x - new.x >= ELECTRIC_THRESHOLD_EDGE_DELTA && new.x <= ELECTRIC_THRESHOLD_LOW) - fallingEdgeIndices.add(index) - - - oldSinkStatus[index].set(new) - } - } - - - risingEdgeIndices.forEach { onRisingEdge(it) } - fallingEdgeIndices.forEach { onFallingEdge(it) } - updateSignal() - - - - /*oldSinkStatus.indices.forEach { index -> - oldSinkStatus[index].set(new) - }*/ - } -} /** * Protip: do not make child classes take any argument, especially no function (function "classes" have no zero-arg constructor) @@ -536,6 +346,8 @@ open class FixtureBase : ActorWithBody, CuedByTerrainChange { makeNoiseAndDust(posX, posY) + onSpawn(posX0, posY0) + return true } @@ -645,6 +457,7 @@ interface CuedByWireChange { fun updateForWireChange(cue: IngameInstance.BlockChangeQueueItem) } +interface DeferredSpawn /** * Standard 32-bit binary flags. diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureTapestry.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureTapestry.kt index cc00da14d..41a141ce3 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureTapestry.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureTapestry.kt @@ -16,7 +16,7 @@ import kotlin.properties.Delegates /** * Created by minjaesong on 2017-01-07. */ -internal class FixtureTapestry : FixtureBase { +internal class FixtureTapestry : FixtureBase, DeferredSpawn { @Transient override val spawnNeedsWall = true @Transient override val spawnNeedsFloor = false