diff --git a/MECHNANICS.md b/MECHNANICS.md index 6f8c4f73d..b2beadf3c 100644 --- a/MECHNANICS.md +++ b/MECHNANICS.md @@ -123,3 +123,16 @@ see SAVE_FORMAT.md * Works like Ampere in electronics. - Health: Regen per time - Magic: Out power per time + + +## Civilisation ## + +Based on _Millénaire_ Minecraft mod, they have limited resources, have ruler (one of those: town-ruler, country-ruler). Players can either help them or force their way into the head position, or even eliminating them. + +Villages can be either independent or part of larger country (_Dwarf Fortress_) + + +## Level of technology ## + +Anything goes as long as it does not exceed TL11 of GURPS. Although the universe has the existence of traditional sorcery, the laws of physics of the universe itself is same as we know it. Simply put: NO FUCKING PERPETUAL MOTION AND PERFECT PROPHECY + diff --git a/MISC_FEATURES.md b/MISC_FEATURES.md index bc67c1857..7f78af1bc 100644 --- a/MISC_FEATURES.md +++ b/MISC_FEATURES.md @@ -3,7 +3,8 @@ ### Looms for custom pattern ### - Players can create their own décors (hang on wall), dresses. -- Two looms (216 colour mode, 4096 colour mode) +- Two looms (16 palette mode, 64 palette mode) +- __IMPLEMENTED__ — see net.torvald.terrarum.gameactors.DecodeTapestry ### Music making ### @@ -25,13 +26,13 @@ Connect two or more tracker head to play the array of trackers play simultaneous .json { - notes = [arr, fixed size of 48], + notes = [arr(64)], // 64 notes per track speed = 120 } - *int: (0-63) number of the note pitch that is struck. 32: Middle C (C3). - 'A' just above of Middle C (A3) has base pitch of 440 Hz. - *speed: in BPM + - key: C1-D6 (63 keys) + - speed: in BPM + - tuning: A440 ## Aimhack ## diff --git a/src/net/torvald/terrarum/StateInGame.kt b/src/net/torvald/terrarum/StateInGame.kt index aa974449f..bd8c6578f 100644 --- a/src/net/torvald/terrarum/StateInGame.kt +++ b/src/net/torvald/terrarum/StateInGame.kt @@ -194,7 +194,7 @@ constructor() : BasicGameState() { /////////////////////////// TilePropUtil.dynamicLumFuncTickClock() world.updateWorldTime(delta) - WorldSimulator(player, delta) + //WorldSimulator(player, delta) WeatherMixer.update(gc, delta) TileStats.update() if (!(CommandDict["setgl"] as SetGlobalLightOverride).lightOverride) @@ -300,8 +300,8 @@ constructor() : BasicGameState() { private fun setAppTitle() { Terrarum.appgc.setTitle( Terrarum.NAME + - " — F: ${Terrarum.appgc.fps} (${Terrarum.TARGET_INTERNAL_FPS})" + - " — M: ${Terrarum.memInUse}M / ${Terrarum.memTotal}M / ${Terrarum.memXmx}M") + " — F: ${Terrarum.appgc.fps} (${Terrarum.TARGET_INTERNAL_FPS})") + //" — M: ${Terrarum.memInUse}M / ${Terrarum.memTotal}M / ${Terrarum.memXmx}M") } override fun render(gc: GameContainer, sbg: StateBasedGame, gwin: Graphics) { @@ -405,12 +405,12 @@ constructor() : BasicGameState() { // velocity worldG.color = GameFontBase.codeToCol["g"] worldG.drawString( - "${0x7F.toChar()}X ${actor.velocity.x}", // doesn't work for NPCs/Player + "${0x7F.toChar()}X ${actor.moveDelta.x}", actor.hitbox.posX.toFloat(), actor.hitbox.pointedY.toFloat() + 4 + 8 ) worldG.drawString( - "${0x7F.toChar()}Y ${actor.velocity.y}", + "${0x7F.toChar()}Y ${actor.moveDelta.y}", actor.hitbox.posX.toFloat(), actor.hitbox.pointedY.toFloat() + 4 + 8 * 2 ) diff --git a/src/net/torvald/terrarum/StateNoiseTester.kt b/src/net/torvald/terrarum/StateNoiseTester.kt new file mode 100644 index 000000000..ab2153c4c --- /dev/null +++ b/src/net/torvald/terrarum/StateNoiseTester.kt @@ -0,0 +1,281 @@ +package net.torvald.terrarum + +import com.sudoplay.joise.Joise +import com.sudoplay.joise.module.* +import net.torvald.random.HQRNG +import net.torvald.terrarum.concurrent.ThreadParallel +import net.torvald.terrarum.gameactors.roundInt +import org.newdawn.slick.Color +import org.newdawn.slick.GameContainer +import org.newdawn.slick.Graphics +import org.newdawn.slick.ImageBuffer +import org.newdawn.slick.state.BasicGameState +import org.newdawn.slick.state.StateBasedGame +import java.util.* + +/** + * WARNING! HAS SERIOUS MEMORY LEAK + * + * Created by SKYHi14 on 2017-01-30. + */ +class StateNoiseTester : BasicGameState() { + + companion object { + val imagesize = 512 + val sampleDensity = 1.0 + val noiseImageBuffer = ImageBuffer(imagesize, imagesize) + var generating = false + } + override fun init(p0: GameContainer?, p1: StateBasedGame?) { + generateNoiseImage() + } + + private fun noise(seed: Long): Joise { + /* Init */ + + val joiseSeed = seed + val lowlandMagic: Long = 0x44A21A114DBE56 // maria lindberg + val highlandMagic: Long = 0x0114E091 // olive oyl + val mountainMagic: Long = 0x115AA4DE2504 // lisa anderson + val selectionMagic: Long = 0x44E10D9B100 // melody blue + + val ground_gradient = ModuleGradient() + ground_gradient.setGradient(0.0, 0.0, 0.0, 1.0) + + /* Lowlands */ + + val lowland_shape_fractal = ModuleFractal() + lowland_shape_fractal.setType(ModuleFractal.FractalType.FBM) + lowland_shape_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) + lowland_shape_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) + lowland_shape_fractal.setNumOctaves(2) + lowland_shape_fractal.setFrequency(1.0) + lowland_shape_fractal.seed = joiseSeed xor lowlandMagic + + val lowland_autocorrect = ModuleAutoCorrect() + lowland_autocorrect.setSource(lowland_shape_fractal) + lowland_autocorrect.setLow(0.0) + lowland_autocorrect.setHigh(1.0) + + val lowland_scale = ModuleScaleOffset() + lowland_scale.setSource(lowland_autocorrect) + lowland_scale.setScale(0.2) + lowland_scale.setOffset(-0.25) + + val lowland_y_scale = ModuleScaleDomain() + lowland_y_scale.setSource(lowland_scale) + lowland_y_scale.setScaleY(0.0) + + val lowland_terrain = ModuleTranslateDomain() + lowland_terrain.setSource(ground_gradient) + lowland_terrain.setAxisYSource(lowland_y_scale) + + + /* highlands */ + + val highland_shape_fractal = ModuleFractal() + highland_shape_fractal.setType(ModuleFractal.FractalType.RIDGEMULTI) + highland_shape_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) + highland_shape_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) + highland_shape_fractal.setNumOctaves(2) + highland_shape_fractal.setFrequency(2.0) + highland_shape_fractal.seed = joiseSeed xor highlandMagic + + val highland_autocorrect = ModuleAutoCorrect() + highland_autocorrect.setSource(highland_shape_fractal) + highland_autocorrect.setLow(0.0) + highland_autocorrect.setHigh(1.0) + + val highland_scale = ModuleScaleOffset() + highland_scale.setSource(highland_autocorrect) + highland_scale.setScale(0.45) + highland_scale.setOffset(0.0) + + val highland_y_scale = ModuleScaleDomain() + highland_y_scale.setSource(highland_scale) + highland_y_scale.setScaleY(0.0) + + val highland_terrain = ModuleTranslateDomain() + highland_terrain.setSource(ground_gradient) + highland_terrain.setAxisYSource(highland_y_scale) + + + /* mountains */ + + val mountain_shape_fractal = ModuleFractal() + mountain_shape_fractal.setType(ModuleFractal.FractalType.BILLOW) + mountain_shape_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) + mountain_shape_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) + mountain_shape_fractal.setNumOctaves(4) + mountain_shape_fractal.setFrequency(1.0) + mountain_shape_fractal.seed = joiseSeed xor mountainMagic + + val mountain_autocorrect = ModuleAutoCorrect() + mountain_autocorrect.setSource(mountain_shape_fractal) + mountain_autocorrect.setLow(0.0) + mountain_autocorrect.setHigh(1.0) + + val mountain_scale = ModuleScaleOffset() + mountain_scale.setSource(mountain_autocorrect) + mountain_scale.setScale(0.75) + mountain_scale.setOffset(0.25) + + val mountain_y_scale = ModuleScaleDomain() + mountain_y_scale.setSource(mountain_scale) + mountain_y_scale.setScaleY(0.1) // controls "quirkiness" of the mountain + + val mountain_terrain = ModuleTranslateDomain() + mountain_terrain.setSource(ground_gradient) + mountain_terrain.setAxisYSource(mountain_y_scale) + + + /* selection */ + + val terrain_type_fractal = ModuleFractal() + terrain_type_fractal.setType(ModuleFractal.FractalType.FBM) + terrain_type_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) + terrain_type_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) + terrain_type_fractal.setNumOctaves(3) + terrain_type_fractal.setFrequency(0.5) + terrain_type_fractal.seed = joiseSeed xor selectionMagic + + val terrain_autocorrect = ModuleAutoCorrect() + terrain_autocorrect.setSource(terrain_type_fractal) + terrain_autocorrect.setLow(0.0) + terrain_autocorrect.setHigh(1.0) + + val terrain_type_cache = ModuleCache() + terrain_type_cache.setSource(terrain_autocorrect) + + val highland_mountain_select = ModuleSelect() + highland_mountain_select.setLowSource(highland_terrain) + highland_mountain_select.setHighSource(mountain_terrain) + highland_mountain_select.setControlSource(terrain_type_cache) + highland_mountain_select.setThreshold(0.55) + highland_mountain_select.setFalloff(0.15) + + val highland_lowland_select = ModuleSelect() + highland_lowland_select.setLowSource(lowland_terrain) + highland_lowland_select.setHighSource(highland_mountain_select) + highland_lowland_select.setControlSource(terrain_type_cache) + highland_lowland_select.setThreshold(0.25) + highland_lowland_select.setFalloff(0.15) + + val ground_select = ModuleSelect() + ground_select.setLowSource(0.0) + ground_select.setHighSource(1.0) + ground_select.setThreshold(0.5) + ground_select.setControlSource(highland_lowland_select) + + + val joise = Joise(ground_select) + return joise + } + + fun generateNoiseImage() { + val noiseModule = noise(HQRNG().nextLong()) // change noise function here + + for (y in 0..imagesize - 1) { + for (x in 0..imagesize - 1) { + noiseImageBuffer.setRGBA(x, y, 0, 0, 0, 255) + } + } + + for (i in 0..Terrarum.THREADS - 1) { + ThreadParallel.map( + i, + ThreadRunNoiseSampling( + imagesize.toFloat().div(Terrarum.THREADS).times(i).roundInt(), + imagesize.toFloat().div(Terrarum.THREADS).times(i.plus(1)).roundInt() - 1, + noiseModule + ), + "SampleJoiseMap" + ) + } + + ThreadParallel.startAll() + } + + override fun update(gc: GameContainer, sbg: StateBasedGame, delta: Int) { + Terrarum.appgc.setTitle("${Terrarum.NAME} — F: ${Terrarum.appgc.fps}" + + " — M: ${Terrarum.memInUse}M / ${Terrarum.totalVMMem}M") + + + if (ThreadParallel.allFinished()) generating = false + } + + override fun getID() = Terrarum.STATE_ID_TOOL_NOISEGEN + + override fun render(gc: GameContainer, sbg: StateBasedGame, g: Graphics) { + g.color = Color.red + g.drawString("Press SPACE to generate new noise", 8f, 8f) + g.drawString("CPUs: ${Terrarum.THREADS}", Terrarum.WIDTH - 90f, 8f) + + g.background = Color.cyan + g.drawImage(noiseImageBuffer.image,//noiseImage, + Terrarum.WIDTH.minus(imagesize).div(2).toFloat(), + Terrarum.HEIGHT.minus(imagesize).div(2).toFloat() + ) + } + + override fun keyPressed(key: Int, c: Char) { + if (c == ' ' && !generating) { + println("Generating noise, may take a while") + generating = true + generateNoiseImage() + } + } + + + class ThreadRunNoiseSampling(val startIndex: Int, val endIndex: Int, val joise: Joise) : Runnable { + /*override fun run() { + for (sy in startIndex..endIndex) { + for (sx in 0..imagesize - 1) { + val y = sy.toDouble() / imagesize + val x = sx.toDouble() / imagesize + + val sampleOffset = sampleDensity + // 4-D toroidal sampling (looped H and V) + val sampleTheta1 = x * Math.PI * 2.0 + val sampleTheta2 = y * Math.PI * 2.0 + val sampleX = Math.sin(sampleTheta1) * sampleDensity + sampleDensity + val sampleY = Math.cos(sampleTheta1) * sampleDensity + sampleDensity + val sampleZ = Math.sin(sampleTheta2) * sampleDensity + sampleDensity + val sampleW = Math.cos(sampleTheta2) * sampleDensity + sampleDensity + + val noise = joise.get( + sampleX, sampleY, sampleZ, sampleW + ) // autocorrection REQUIRED! + + val noiseCol = noise.times(255f).toInt() + noiseImageBuffer.setRGBA(sx, sy, noiseCol, noiseCol, noiseCol, 255) + + } + } + }*/ + + override fun run() { + for (sy in startIndex..endIndex) { + for (sx in 0..imagesize - 1) { + val y = sy.toDouble() / imagesize * 1.5 -.6 + val x = sx.toDouble() / imagesize + + val sampleOffset = sampleDensity + // 4-D toroidal sampling (looped H and V) + val sampleTheta1 = x * Math.PI * 2.0 + val sampleX = Math.sin(sampleTheta1) * sampleDensity + sampleDensity + val sampleZ = Math.cos(sampleTheta1) * sampleDensity + sampleDensity + val sampleY = y + + val noise = joise.get( + sampleX, sampleY, sampleZ + ) // autocorrection REQUIRED! + + val noiseCol = noise.times(255f).toInt() + noiseImageBuffer.setRGBA(sx, sy, noiseCol, noiseCol, noiseCol, 255) + + } + } + } + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index 7a290ae64..f443d8044 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -133,6 +133,7 @@ constructor(gamename: String) : StateBasedGame(gamename) { //addState(StateNoiseTexGen()) //addState(StateBlurTest()) //addState(StateShaderTest()) + //addState(StateNoiseTester()) ingame = StateInGame() addState(ingame) diff --git a/src/net/torvald/terrarum/console/Authenticator.kt b/src/net/torvald/terrarum/console/Authenticator.kt index 5f3057239..2c8fe1222 100644 --- a/src/net/torvald/terrarum/console/Authenticator.kt +++ b/src/net/torvald/terrarum/console/Authenticator.kt @@ -5,6 +5,23 @@ import net.torvald.terrarum.ui.ConsoleWindow import org.apache.commons.codec.digest.DigestUtils /** + * + * Password setting rules: + * + * For each releases new password should be set. The new password must: + * - start with next alphabet of previous password + * if previous password started with Z, the new password must start with A + * - be a name appear in the Legend of Zelda series which officially released by Nintendo + * - be lowercase + * - BE CRACKABLE (crackstation.net) + * + * Example passwords would be: + * aryll -> biggoron -> ciela -> ... -> linebeck -> midna -> navi -> ... + * + * Notes: + * do NOT put plaintext anywhere in the code (except for comments maybe) + * must use SHA-256 + * * Created by minjaesong on 16-02-19. */ internal object Authenticator : ConsoleCommand { diff --git a/src/net/torvald/terrarum/gameactors/Actor.kt b/src/net/torvald/terrarum/gameactors/Actor.kt index af1e58e65..eeb98c3db 100644 --- a/src/net/torvald/terrarum/gameactors/Actor.kt +++ b/src/net/torvald/terrarum/gameactors/Actor.kt @@ -39,7 +39,7 @@ abstract class Actor(val renderOrder: ActorOrder) : Comparable, Runnable * override var referenceID: Int = generateUniqueReferenceID() */ fun generateUniqueReferenceID(): Int { - fun itIsNotValid(value: Int) = + fun checkForCollision(value: Int) = Terrarum.ingame.theGameHasActor(value) || value < ItemCodex.ITEM_COUNT_MAX || value < when (renderOrder) { @@ -58,7 +58,7 @@ abstract class Actor(val renderOrder: ActorOrder) : Comparable, Runnable var ret: Int do { ret = HQRNG().nextInt().and(0x7FFFFFFF) // set new ID - } while (itIsNotValid(ret)) // check for collision + } while (checkForCollision(ret)) // check for collision return ret } diff --git a/src/net/torvald/terrarum/gameactors/ActorWithSprite.kt b/src/net/torvald/terrarum/gameactors/ActorWithSprite.kt index 9a4d8becf..8d4a94a10 100644 --- a/src/net/torvald/terrarum/gameactors/ActorWithSprite.kt +++ b/src/net/torvald/terrarum/gameactors/ActorWithSprite.kt @@ -65,39 +65,31 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = ) /** + * Elevators/Movingwalks/etc.: edit hitbox manually! + * * Velocity vector for newtonian sim. * Acceleration: used in code like: * veloY += 3.0 * +3.0 is acceleration. You __accumulate__ acceleration to the velocity. */ - internal val velocity = Vector2(0.0, 0.0) - var veloX: Double - get() = velocity.x - protected set(value) { - velocity.x = value - } - var veloY: Double - get() = velocity.y - protected set(value) { - velocity.y = value - } + internal val externalForce = Vector2(0.0, 0.0) - val moveDelta = Vector2(0.0, 0.0) + val moveDelta = Vector2(0.0, 0.0) // moveDelta = velocity + controllerMoveDelta @Transient private val VELO_HARD_LIMIT = 100.0 /** * for "Controllable" actors */ - var controllerVel: Vector2? = if (this is Controllable) Vector2() else null + var controllerMoveDelta: Vector2? = if (this is Controllable) Vector2() else null var walkX: Double - get() = controllerVel!!.x + get() = controllerMoveDelta!!.x protected set(value) { - controllerVel!!.x = value + controllerMoveDelta!!.x = value } var walkY: Double - get() = controllerVel!!.y + get() = controllerMoveDelta!!.y protected set(value) { - controllerVel!!.y = value + controllerMoveDelta!!.y = value } /** @@ -155,8 +147,6 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = } get() = 1.0 - elasticity - @Transient private val CEILING_HIT_ELASTICITY = 0.3 - var density = 1000.0 set(value) { if (value < 0) @@ -342,7 +332,7 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = * @param acc : Acceleration in Vector2 */ fun applyForce(acc: Vector2) { - velocity += acc.times(speedMultByTile) + externalForce += acc * speedMultByTile } private val bounceDampenVelThreshold = 0.5 @@ -375,8 +365,8 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = } // hard limit velocity - veloX = veloX.bipolarClamp(VELO_HARD_LIMIT) - veloY = veloY.bipolarClamp(VELO_HARD_LIMIT) + externalForce.x = externalForce.x.bipolarClamp(VELO_HARD_LIMIT) + externalForce.y = externalForce.y.bipolarClamp(VELO_HARD_LIMIT) // Set 'next' position (hitbox) from canonical and walking velocity setNewNextHitbox() @@ -391,8 +381,12 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = applyNormalForce() } - setHorizontalFriction() - if (immobileBody || isPlayerNoClip) { // TODO also hanging on the rope, etc. + if (!immobileBody) { // TODO test no friction on immobileBody + setHorizontalFriction() + } + //if (immobileBody || isPlayerNoClip) { // TODO also hanging on the rope, etc. + // TODO test no friction on immobileBody + if (isPlayerNoClip) { // TODO also hanging on the rope, etc. setVerticalFriction() } @@ -429,28 +423,28 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = if (!(isCollidingSide(hitbox, COLLIDING_LEFT) && walkX < 0) || !(isCollidingSide(hitbox, COLLIDING_RIGHT) && walkX > 0) ) { - moveDelta.x = veloX + walkX + moveDelta.x = externalForce.x + walkX } // decide whether to ignore walkY if (!(isCollidingSide(hitbox, COLLIDING_TOP) && walkY < 0) || !(isCollidingSide(hitbox, COLLIDING_BOTTOM) && walkY > 0) ) { - moveDelta.y = veloY + walkY + moveDelta.y = externalForce.y + walkY } } else { if (!isCollidingSide(hitbox, COLLIDING_LEFT) || !isCollidingSide(hitbox, COLLIDING_RIGHT) ) { - moveDelta.x = veloX + moveDelta.x = externalForce.x } // decide whether to ignore walkY if (!isCollidingSide(hitbox, COLLIDING_TOP) || !isCollidingSide(hitbox, COLLIDING_BOTTOM) ) { - moveDelta.y = veloY + moveDelta.y = externalForce.y } } } @@ -466,18 +460,18 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = * weight; gravitational force in action * W = mass * G (9.8 [m/s^2]) */ - val W: Vector2 = gravitation * mass + val W: Vector2 = gravitation * Terrarum.TARGET_FPS.toDouble() /** * Area */ - val A: Double = scale * scale + val A: Double = (scale * baseHitboxW / METER) * (scale * baseHitboxW / METER) /** * Drag of atmosphere * D = Cd (drag coefficient) * 0.5 * rho (density) * V^2 (velocity sqr) * A (area) */ - val D: Vector2 = Vector2(veloX.magnSqr(), veloY.magnSqr()) * dragCoefficient * 0.5 * A// * tileDensityFluid.toDouble() + val D: Vector2 = Vector2(moveDelta.x.magnSqr(), moveDelta.y.magnSqr()) * dragCoefficient * 0.5 * A// * tileDensityFluid.toDouble() - val V: Vector2 = (W - D) / mass * SI_TO_GAME_ACC + val V: Vector2 = (W - D) / Terrarum.TARGET_FPS.toDouble() * SI_TO_GAME_ACC applyForce(V) } @@ -488,7 +482,7 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = // axis Y. Using operand >= and hitting the ceiling will lock the player to the position if (moveDelta.y > 0.0) { // was moving downward? if (isColliding(nextHitbox, COLLIDING_TOP)) { // hit the ceiling - hitAndForciblyReflectY() + hitAndReflectY() //hitAndForciblyReflectY() grounded = false } else if (isColliding(nextHitbox)) { @@ -512,9 +506,12 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = } } // axis X - if (isTouchingSide(nextHitbox, COLLIDING_LEFT) || isTouchingSide(nextHitbox, COLLIDING_RIGHT) - && moveDelta.x != 0.0) { // check right and left + if (isTouchingSide(nextHitbox, COLLIDING_LEFT) || isTouchingSide(nextHitbox, COLLIDING_RIGHT)) { // check right and left // the actor is hitting the wall + + // FIXME balls are stuck in this + if (referenceID != 321321321) + println("$this trying to reflectX") hitAndReflectX() } } @@ -534,7 +531,7 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = val ccdDelta = (nextHitbox.toVector() - hitbox.toVector()) if (ccdDelta.x != 0.0 || ccdDelta.y != 0.0) { //ccdDelta.set(ccdDelta.setMagnitude(CCD_TICK)) // fixed tick - val displacement = Math.min(1.0.div(velocity.magnitude * 2), 0.5) // adaptive tick + val displacement = Math.min(1.0.div(moveDelta.magnitude * 2), 0.5) // adaptive tick ccdDelta.set(ccdDelta.setMagnitude(displacement)) } @@ -551,35 +548,44 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = } private fun hitAndReflectX() { - if ((veloX * elasticity).abs() > Epsilon.E) { - veloX *= -elasticity + if ((externalForce.x * elasticity).abs() >= MINIMUM_BOUNCE_THRESHOLD) { // > Epsilon.E) { + externalForce.x *= -elasticity if (this is Controllable) walkX *= -elasticity } else { - veloX = 0.0 + externalForce.x = 0.0 if (this is Controllable) walkX = 0.0 } } private fun hitAndReflectY() { - if ((veloY * elasticity).abs() > Epsilon.E) { - veloY *= -elasticity + if (externalForce.y.abs() >= MINIMUM_BOUNCE_THRESHOLD) { //> Epsilon.E) { + externalForce.y *= -elasticity if (this is Controllable) walkY *= -elasticity } else { - veloY = 0.0 + externalForce.y = 0.0 if (this is Controllable) walkY *= 0.0 } } + @Transient private val CEILING_HIT_ELASTICITY = 0.3 + @Transient private val MINIMUM_BOUNCE_THRESHOLD = 0.1 + /** * prevents sticking to the ceiling */ private fun hitAndForciblyReflectY() { - if (veloY.abs() * CEILING_HIT_ELASTICITY > A_PIXEL) - veloY = -veloY * CEILING_HIT_ELASTICITY - else - veloY = veloY.sign() * -A_PIXEL + // TODO HARK! I have changed veloX/Y to moveDelta.x/y + if (moveDelta.y < 0) { + if (moveDelta.y.abs() * CEILING_HIT_ELASTICITY > A_PIXEL) + moveDelta.y = -moveDelta.y * CEILING_HIT_ELASTICITY + else + moveDelta.y = moveDelta.y.sign() * -A_PIXEL + } + else { + throw Error("Check this out bitch (moveDelta.y = ${moveDelta.y})") + } } private fun isColliding(hitbox: Hitbox) = isColliding(hitbox, 0) @@ -792,13 +798,13 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = BASE_FRICTION * if (grounded) feetFriction else bodyFriction } - if (veloX < 0) { - veloX += friction - if (veloX > 0) veloX = 0.0 // compensate overshoot + if (externalForce.x < 0) { + externalForce.x += friction + if (externalForce.x > 0) externalForce.x = 0.0 // compensate overshoot } - else if (veloX > 0) { - veloX -= friction - if (veloX < 0) veloX = 0.0 // compensate overshoot + else if (externalForce.x > 0) { + externalForce.x -= friction + if (externalForce.x < 0) externalForce.x = 0.0 // compensate overshoot } if (this is Controllable) { @@ -819,13 +825,13 @@ open class ActorWithSprite(renderOrder: ActorOrder, val immobileBody: Boolean = else BASE_FRICTION * bodyFriction - if (veloY < 0) { - veloY += friction - if (veloY > 0) veloX = 0.0 // compensate overshoot + if (externalForce.y < 0) { + externalForce.y += friction + if (externalForce.y > 0) externalForce.y = 0.0 // compensate overshoot } - else if (veloY > 0) { - veloY -= friction - if (veloY < 0) veloY = 0.0 // compensate overshoot + else if (externalForce.y > 0) { + externalForce.y -= friction + if (externalForce.y < 0) externalForce.y = 0.0 // compensate overshoot } if (this is Controllable) { diff --git a/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt b/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt index e8505fd7b..249c429bb 100644 --- a/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt +++ b/src/net/torvald/terrarum/gameactors/PlayerBuilderSigrid.kt @@ -56,7 +56,7 @@ object PlayerBuilderSigrid { p.actorValue[AVKey.INTELLIGENT] = true - //p.actorValue[AVKey.LUMINOSITY] = Color(0x434aff).to10bit() + p.actorValue[AVKey.LUMINOSITY] = Color(0x434aff).to10bit() p.actorValue[AVKey.BASEDEFENCE] = 141 diff --git a/src/net/torvald/terrarum/gameactors/ProjectileSimple.kt b/src/net/torvald/terrarum/gameactors/ProjectileSimple.kt index 201d42027..06569757c 100644 --- a/src/net/torvald/terrarum/gameactors/ProjectileSimple.kt +++ b/src/net/torvald/terrarum/gameactors/ProjectileSimple.kt @@ -53,7 +53,7 @@ open class ProjectileSimple( 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.velocity.set(velocity) + //this.externalForce.set(velocity) damage = bulletDatabase[type][OFFSET_DAMAGE] as Int displayColour = bulletDatabase[type][OFFSET_COL] as Color @@ -63,7 +63,7 @@ open class ProjectileSimple( setHitboxDimension(2, 2, 0, 0) // should be following sprite's properties if there IS one - velocity.set((fromPoint to toPoint).setMagnitude(speed.toDouble())) + externalForce.set((fromPoint to toPoint).setMagnitude(speed.toDouble())) diff --git a/src/net/torvald/terrarum/gameactors/ai/AILuaAPI.kt b/src/net/torvald/terrarum/gameactors/ai/AILuaAPI.kt index 04345f729..47f172bb7 100644 --- a/src/net/torvald/terrarum/gameactors/ai/AILuaAPI.kt +++ b/src/net/torvald/terrarum/gameactors/ai/AILuaAPI.kt @@ -60,8 +60,8 @@ internal class AILuaAPI(g: Globals, actor: ActorWithSprite) { t["posX"] = actor.hitbox.centeredX.toLua() t["posY"] = actor.hitbox.centeredY.toLua() - t["veloX"] = actor.veloX.toLua() - t["veloY"] = actor.veloY.toLua() + t["veloX"] = actor.moveDelta.x.toLua() + t["veloY"] = actor.moveDelta.y.toLua() t["width"] = actor.hitbox.width.toLua() t["height"] = actor.hitbox.height.toLua() diff --git a/src/net/torvald/terrarum/gameactors/physicssolver/CollisionSolver.kt b/src/net/torvald/terrarum/gameactors/physicssolver/CollisionSolver.kt index 9b81a67c4..fcc9f0561 100644 --- a/src/net/torvald/terrarum/gameactors/physicssolver/CollisionSolver.kt +++ b/src/net/torvald/terrarum/gameactors/physicssolver/CollisionSolver.kt @@ -150,10 +150,10 @@ object CollisionSolver { // if they actually makes collision (e.g. player vs ball), solve it if (a makesCollisionWith b) { - val ux_1 = a.veloX - val ux_2 = b.veloX - val uy_1 = a.veloY - val uy_2 = b.veloY + 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 diff --git a/src/net/torvald/terrarum/mapgenerator/ThreadProcessNoiseLayers.kt b/src/net/torvald/terrarum/mapgenerator/ThreadProcessNoiseLayers.kt index 908f7ce5c..3327930bc 100644 --- a/src/net/torvald/terrarum/mapgenerator/ThreadProcessNoiseLayers.kt +++ b/src/net/torvald/terrarum/mapgenerator/ThreadProcessNoiseLayers.kt @@ -24,8 +24,8 @@ class ThreadProcessNoiseLayers(val startIndex: Int, val endIndex: Int, val sampleTheta = (x.toDouble() / WorldGenerator.WIDTH) * WorldGenerator.TWO_PI val sampleOffset = (WorldGenerator.WIDTH / sampleDensity) / 8.0 val sampleX = Math.sin(sampleTheta) * sampleOffset + sampleOffset // plus sampleOffset to make only - val sampleY = Math.cos(sampleTheta) * sampleOffset + sampleOffset // positive points are to be sampled - val sampleZ = y / sampleDensity + val sampleZ = Math.cos(sampleTheta) * sampleOffset + sampleOffset // positive points are to be sampled + val sampleY = y / sampleDensity val noise: Double = record.noiseModule.get(sampleX, sampleY, sampleZ) val fromTerr = record.replaceFromTerrain diff --git a/src/net/torvald/terrarum/mapgenerator/WorldGenerator.kt b/src/net/torvald/terrarum/mapgenerator/WorldGenerator.kt index b2daecf0b..293557d5a 100644 --- a/src/net/torvald/terrarum/mapgenerator/WorldGenerator.kt +++ b/src/net/torvald/terrarum/mapgenerator/WorldGenerator.kt @@ -38,9 +38,7 @@ object WorldGenerator { private val NOISE_SIMPLEX_ORE_START = 1.42 private val NOISE_SIMPLEX_ORE_END = 1.28 - private val HILL_WIDTH = 256 // power of two! - //private val MAX_HILL_HEIGHT = 100 - private val TERRAIN_UNDULATION = 250 + private val TERRAIN_UNDULATION = 200 private val SIMPLEXGEN_LARGEST_FEATURE = 200 @@ -51,8 +49,8 @@ object WorldGenerator { private var GLACIER_MOUNTAIN_WIDTH = 900 private val GLACIER_MOUNTAIN_HEIGHT = 300 - private val CAVEGEN_THRE_START = 0.95 - private val CAVEGEN_THRE_END = 0.67 + private val CAVEGEN_THRE_START = 0.4 + private val CAVEGEN_THRE_END = 0.1 private var worldOceanPosition: Int = -1 @@ -109,12 +107,18 @@ object WorldGenerator { * Todo: Lakes! Aquifers! Lava chambers! * Todo: deserts (variants: SAND_DESERT, SAND_RED) * Todo: volcano(es?) - * Done: variants of beach (SAND, SAND_BEACH, SAND_BLACK, SAND_GREEN) + * TODO: variants of beach (SAND, SAND_BEACH, SAND_BLACK, SAND_GREEN) + * + * + * Hark! We use cylindrical sampling + * + * x, z: X-axis sampling + * y: Y-axis sampling */ - val noiseArray = arrayOf( - TaggedJoise("Carving caves", noiseRidged(1.7, 1.4), 1.0, TILE_MACRO_ALL, TILE_MACRO_ALL, Tile.AIR, NoiseFilterSqrt, CAVEGEN_THRE_START, CAVEGEN_THRE_END) - , TaggedJoise("Collapsing caves", noiseBlobs(0.5), 0.3, Tile.AIR, Tile.STONE, Tile.STONE, NoiseFilterUniform) + // TODO cave one featured in http://accidentalnoise.sourceforge.net/minecraftworlds.html + TaggedJoise("Carving caves", noiseCave(), 1.0, TILE_MACRO_ALL, TILE_MACRO_ALL, Tile.AIR, NoiseFilterSqrt, CAVEGEN_THRE_START, CAVEGEN_THRE_END) +// , TaggedJoise("Collapsing caves", noiseBlobs(0.5), 0.3, Tile.AIR, Tile.STONE, Tile.STONE, NoiseFilterUniform) // //, TaggedJoise("Putting stone patches on the ground", noiseBlobs(0.8), 1.02f, intArrayOf(Tile.DIRT, Tile.GRASS), Tile.DIRT, Tile.STONE, NoiseFilterQuadratic, NOISE_GRAD_END, NOISE_GRAD_START) //, TaggedJoise("Placing dirt spots in the cave", noiseBlobs(0.5), 0.98f, Tile.STONE, Tile.STONE, Tile.DIRT, NoiseFilterQuadratic, NOISE_GRAD_END, NOISE_GRAD_START) @@ -179,19 +183,69 @@ object WorldGenerator { val ridged_scale = ModuleScaleDomain() ridged_scale.setScaleX(xStretch.toDouble()) - ridged_scale.setScaleY(yStretch.toDouble()) + ridged_scale.setScaleY(xStretch.toDouble()) + ridged_scale.setScaleZ(yStretch.toDouble()) ridged_scale.setSource(ridged_autocorrect) return Joise(ridged_scale) } + private fun noiseCave(): Joise { + val caveMagic: Long = 0x00215741CDF // Urist McDF + val cavePerturbMagic: Long = 0xA2410C // Armok + val arbitraryScale = 4.0 + + + val cave_shape = ModuleFractal() + cave_shape.setType(ModuleFractal.FractalType.RIDGEMULTI) + cave_shape.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) + cave_shape.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) + cave_shape.setNumOctaves(1) + cave_shape.setFrequency(4.0) + cave_shape.seed = SEED xor caveMagic + + val cave_select = ModuleSelect() + cave_select.setLowSource(1.0) + cave_select.setHighSource(0.0) + cave_select.setControlSource(cave_shape) + cave_select.setThreshold(0.8) + cave_select.setFalloff(0.0) + + val cave_perturb_fractal = ModuleFractal() + cave_perturb_fractal.setType(ModuleFractal.FractalType.FBM) + cave_perturb_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) + cave_perturb_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) + cave_perturb_fractal.setNumOctaves(6) + cave_perturb_fractal.setFrequency(3.0) + cave_perturb_fractal.seed = SEED xor cavePerturbMagic + + val cave_perturb_scale = ModuleScaleOffset() + cave_perturb_scale.setSource(cave_perturb_fractal) + cave_perturb_scale.setScale(0.5) + cave_perturb_scale.setOffset(0.0) + + val cave_perturb = ModuleTranslateDomain() + cave_perturb.setSource(cave_perturb_fractal) + cave_perturb.setAxisXSource(cave_perturb_scale) + + val cave_scale = ModuleScaleDomain() + cave_scale.setScaleX(1.0 / arbitraryScale) + cave_scale.setScaleZ(1.0 / arbitraryScale) + cave_scale.setScaleY(1.0 / arbitraryScale) + cave_scale.setSource(cave_perturb) + + return Joise(cave_scale) + } + private fun noiseBlobs(frequency: Double): Joise { + val ridgedMagic: Long = 0x4114EC2AF7 // minecraft + val ridged = ModuleFractal() ridged.setType(ModuleFractal.FractalType.FBM) ridged.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) ridged.setNumOctaves(2) ridged.setFrequency(frequency) - ridged.seed = Random().nextLong() + ridged.seed = SEED xor ridgedMagic val brownian_select = ModuleSelect() brownian_select.setControlSource(ridged) @@ -269,8 +323,15 @@ object WorldGenerator { val noiseMap = Array(HEIGHT, { BitSet(WIDTH) }) // Height = Terrain undulation times 2. - val SCALE_X: Double = (TERRAIN_UNDULATION * 0.5).toDouble() - val SCALE_Y: Double = (TERRAIN_UNDULATION * 0.25).toDouble() + val SCALE_X: Double = TERRAIN_UNDULATION * 1.33 + val SCALE_Y: Double = TERRAIN_UNDULATION * 1.0 + + /* Init */ + + val lowlandMagic: Long = 0x41A21A114DBE56 // Maria Lindberg + val highlandMagic: Long = 0x0114E091 // Olive Oyl + val mountainMagic: Long = 0x115AA4DE2504 // Lisa Anderson + val selectionMagic: Long = 0x41E10D9B100 // Melody Blue val ground_gradient = ModuleGradient() ground_gradient.setGradient(0.0, 0.0, 0.0, 1.0) @@ -281,19 +342,19 @@ object WorldGenerator { lowland_shape_fractal.setType(ModuleFractal.FractalType.FBM) lowland_shape_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) lowland_shape_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) - lowland_shape_fractal.setNumOctaves(4) - lowland_shape_fractal.setFrequency(0.6) - lowland_shape_fractal.seed = SEED xor random.nextLong() - //println(lowland_shape_fractal.seed) + lowland_shape_fractal.setNumOctaves(2) + lowland_shape_fractal.setFrequency(1.0) + lowland_shape_fractal.seed = SEED xor lowlandMagic val lowland_autocorrect = ModuleAutoCorrect() - lowland_autocorrect.setRange(0.0, 1.0) lowland_autocorrect.setSource(lowland_shape_fractal) + lowland_autocorrect.setLow(0.0) + lowland_autocorrect.setHigh(1.0) val lowland_scale = ModuleScaleOffset() lowland_scale.setSource(lowland_autocorrect) - lowland_scale.setScale(0.8) - lowland_scale.setOffset(-2.75) + lowland_scale.setScale(0.2) + lowland_scale.setOffset(-0.25) val lowland_y_scale = ModuleScaleDomain() lowland_y_scale.setSource(lowland_scale) @@ -303,25 +364,26 @@ object WorldGenerator { lowland_terrain.setSource(ground_gradient) lowland_terrain.setAxisYSource(lowland_y_scale) + /* highlands */ val highland_shape_fractal = ModuleFractal() highland_shape_fractal.setType(ModuleFractal.FractalType.RIDGEMULTI) highland_shape_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) highland_shape_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) - highland_shape_fractal.setNumOctaves(4) - highland_shape_fractal.setFrequency(0.5) // horizontal size. Higher == narrower - highland_shape_fractal.seed = SEED xor random.nextLong() - //println(highland_shape_fractal.seed) + highland_shape_fractal.setNumOctaves(2) + highland_shape_fractal.setFrequency(2.0) + highland_shape_fractal.seed = SEED xor highlandMagic val highland_autocorrect = ModuleAutoCorrect() highland_autocorrect.setSource(highland_shape_fractal) - highland_autocorrect.setRange(0.0, 1.0) + highland_autocorrect.setLow(0.0) + highland_autocorrect.setHigh(1.0) val highland_scale = ModuleScaleOffset() highland_scale.setSource(highland_autocorrect) - highland_scale.setScale(1.4) // vertical size. Higher == taller - highland_scale.setOffset(-2.25) + highland_scale.setScale(0.45) + highland_scale.setOffset(0.0) val highland_y_scale = ModuleScaleDomain() highland_y_scale.setSource(highland_scale) @@ -331,55 +393,53 @@ object WorldGenerator { highland_terrain.setSource(ground_gradient) highland_terrain.setAxisYSource(highland_y_scale) + /* mountains */ val mountain_shape_fractal = ModuleFractal() mountain_shape_fractal.setType(ModuleFractal.FractalType.BILLOW) mountain_shape_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) mountain_shape_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) - mountain_shape_fractal.setNumOctaves(6) - mountain_shape_fractal.setFrequency(0.55) - mountain_shape_fractal.seed = SEED xor random.nextLong() - //println(mountain_shape_fractal.seed) + mountain_shape_fractal.setNumOctaves(4) + mountain_shape_fractal.setFrequency(1.0) + mountain_shape_fractal.seed = SEED xor mountainMagic val mountain_autocorrect = ModuleAutoCorrect() mountain_autocorrect.setSource(mountain_shape_fractal) - mountain_autocorrect.setRange(0.0, 1.0) + mountain_autocorrect.setLow(0.0) + mountain_autocorrect.setHigh(1.0) val mountain_scale = ModuleScaleOffset() mountain_scale.setSource(mountain_autocorrect) - mountain_scale.setScale(1.66) - mountain_scale.setOffset(-1.25) + mountain_scale.setScale(0.75) + mountain_scale.setOffset(0.25) val mountain_y_scale = ModuleScaleDomain() mountain_y_scale.setSource(mountain_scale) - mountain_y_scale.setScaleY(0.1) + mountain_y_scale.setScaleY(0.1) // controls "quirkiness" of the mountain val mountain_terrain = ModuleTranslateDomain() mountain_terrain.setSource(ground_gradient) mountain_terrain.setAxisYSource(mountain_y_scale) + /* selection */ val terrain_type_fractal = ModuleFractal() - terrain_type_fractal.setType(ModuleFractal.FractalType.MULTI) + terrain_type_fractal.setType(ModuleFractal.FractalType.FBM) terrain_type_fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT) terrain_type_fractal.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC) - terrain_type_fractal.setNumOctaves(5) - terrain_type_fractal.setFrequency(0.4) // <= 0.33 - terrain_type_fractal.seed = SEED xor random.nextLong() - //println(terrain_type_fractal.seed) + terrain_type_fractal.setNumOctaves(3) + terrain_type_fractal.setFrequency(0.5) + terrain_type_fractal.seed = SEED xor selectionMagic val terrain_autocorrect = ModuleAutoCorrect() terrain_autocorrect.setSource(terrain_type_fractal) - terrain_autocorrect.setRange(0.0, 1.0) - - val terrain_type_scale = ModuleScaleDomain() - terrain_type_scale.setScaleY(0.33) - terrain_type_scale.setSource(terrain_autocorrect) + terrain_autocorrect.setLow(0.0) + terrain_autocorrect.setHigh(1.0) val terrain_type_cache = ModuleCache() - terrain_type_cache.setSource(terrain_type_scale) + terrain_type_cache.setSource(terrain_autocorrect) val highland_mountain_select = ModuleSelect() highland_mountain_select.setLowSource(highland_terrain) @@ -395,13 +455,13 @@ object WorldGenerator { highland_lowland_select.setThreshold(0.25) highland_lowland_select.setFalloff(0.15) - val ground_select = ModuleSelect() ground_select.setLowSource(0.0) ground_select.setHighSource(1.0) ground_select.setThreshold(0.5) ground_select.setControlSource(highland_lowland_select) + val joise = Joise(ground_select) // fill the area as Joise map @@ -414,17 +474,17 @@ object WorldGenerator { x / SCALE_X, y / SCALE_Y ) == 1.0)*/ - // circular sampling + // cylindrical sampling // Mapping function: // World(x, y) -> Joise(sin x, y, cos x) val sampleTheta = (x.toDouble() / WIDTH) * TWO_PI val sampleOffset = (WIDTH / SCALE_X) / 4.0 val sampleX = Math.sin(sampleTheta) * sampleOffset + sampleOffset // plus sampleOffset to make only val sampleZ = Math.cos(sampleTheta) * sampleOffset + sampleOffset // positive points are to be sampled - val sampleY = y / SCALE_Y - val map: Boolean = ( - joise.get(sampleX, sampleY, sampleZ) == 1.0 - ) + val sampleY = y / SCALE_Y * 1.5 - 0.6 + val map: Boolean = joise.get(sampleX, sampleY, sampleZ) == 1.0 + + // FIXME joise.get(sampleX, sampleY, sampleZ) returns all zero noiseMap[y + TERRAIN_AVERAGE_HEIGHT - (TERRAIN_UNDULATION / 2)].set(x, map) } } diff --git a/work_files/graphics/colourmap/pal64_ryb_test_chart.png b/work_files/graphics/colourmap/pal64_ryb_test_chart.png index 7a5dd3134..86060f3ea 100644 Binary files a/work_files/graphics/colourmap/pal64_ryb_test_chart.png and b/work_files/graphics/colourmap/pal64_ryb_test_chart.png differ