From 6ccc964d05ade348364d1608cfd897796ba37720 Mon Sep 17 00:00:00 2001 From: Song Minjae Date: Mon, 30 Jan 2017 22:39:34 +0900 Subject: [PATCH] caves and stuff Former-commit-id: 248077312d8561ad01819a43a4c4a5205c122ff4 Former-commit-id: f4dc915a66bd26338376b9d6008f136fb36b3060 --- MISC_FEATURES.md | 8 +- src/net/torvald/terrarum/StateNoiseTester.kt | 281 ++++++++++++++++++ src/net/torvald/terrarum/Terrarum.kt | 1 + .../gameactors/PlayerBuilderSigrid.kt | 2 +- .../mapgenerator/ThreadProcessNoiseLayers.kt | 4 +- .../terrarum/mapgenerator/WorldGenerator.kt | 77 ++++- .../colourmap/pal64_ryb_test_chart.png | Bin 20003 -> 20178 bytes 7 files changed, 355 insertions(+), 18 deletions(-) create mode 100644 src/net/torvald/terrarum/StateNoiseTester.kt diff --git a/MISC_FEATURES.md b/MISC_FEATURES.md index 7ea50e9a5..7f78af1bc 100644 --- a/MISC_FEATURES.md +++ b/MISC_FEATURES.md @@ -26,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/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 f4e360105..d41f9d040 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/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/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 4b551e9e5..293557d5a 100644 --- a/src/net/torvald/terrarum/mapgenerator/WorldGenerator.kt +++ b/src/net/torvald/terrarum/mapgenerator/WorldGenerator.kt @@ -49,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 @@ -108,12 +108,17 @@ object WorldGenerator { * Todo: deserts (variants: SAND_DESERT, SAND_RED) * Todo: volcano(es?) * 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( // TODO cave one featured in http://accidentalnoise.sourceforge.net/minecraftworlds.html - 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) + 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) @@ -178,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) @@ -273,10 +328,10 @@ object WorldGenerator { /* Init */ - 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 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) diff --git a/work_files/graphics/colourmap/pal64_ryb_test_chart.png b/work_files/graphics/colourmap/pal64_ryb_test_chart.png index 7a5dd31345c970736c0506a3cdd7b5bf01a58e68..86060f3eaaf503fa912ccfa5e44c2b988b5e05b6 100644 GIT binary patch delta 2246 zcma)-eK^y5AIE=N^N>Z`LJ67CgAVp&W-?k$5;>9KbV{?$FOgLqcTQ>Zl#x)oRTFxU zV&rf}sYQ7>iHJxg&r%9!Bu^o~+jZUNy6?LB=l))=zuw>P`}6*MKmUAkGGVP*uwsb@ z+}2c*u932yV!s*@8&3wX(7$5ac_NJK!)YNvdWP zifTb1T99a-B(klQwJm`tG0?L9QRHa7Gv$IH(chZqZ{bhl(JZJGD;o25$0DvGU0{|sX){TRtIEcj*lW`CkgjixRSqiWq02DJ7GMo|w#H=MN zSt^w(m)?UiB?yU`Vh{&`AjFhGEQL%ADI*z>cyoOkKL|L7C3e)z#cIH59G={bQ~r?QkU8{mmb3k0YiB&Wc2U`nT+Oul{trzj<_d^9A9V zs1@sOvsI2EumJqO_~qZsQcO?I736#5)o=8H8Nj~t7vg-GV>!LPn!YoRSuJTvUKL1# zpgPQ~o)_TsO%*HgNHb-{9`(_Aj^3U76A^1X2LR)^A~7yl+lUN^fRrpCWOs-2-2w4D*)#Br*YD zsBbv9d0`>Qe7DA8!w9b9M!icw#7C>Y8|c}cK|Q*q>0kSo8Nh@k-;XAawyoIijS)Xl zY|pe7e5Kg6xM4%_Pk(m%7^mkOw(^GvZ_Zthu6wfunc#Nk8pW}8hqzbx#lzjhnHF&k zPCpDTj#iB{qg2ooVxidy@G}D`)m?T(6!8A+%U^pUW~}Q z+0%P@Gs^20=YUz9|AYSQZ6^-*@?F#o?##aI2lrNANLIzEyTD%e+)Cr1ve!*L6ZcW{ zr%rpoPbxHXNG9=z?jPR5PRPuN*Y~^H#yyq6*gGt&@>6$L-C+Ixqai#uO%9~c}U^hCYNMwlLIf4#A9(hFQ<`MIDd7B2lF+6>} zFiln~jBbg?j>lL!KeDc6Ihi!1d=MD<-RgkFLnHx z(s{man7ZR$U5YaEN$oTLv_rtH?gTmSWhSCCaq4*+deh>)31eXX^uDb9;hRHZkv{FB zs?B)0!(V_=bPYRD+Fr}wx>pL&dxKdNOS$k;k50Bx)x+{e4*9QzLC5p#f=?yuZIVe< zkC@9CwHj_AUhPaw&~NPWfKBCQ$KnDpCN0%{uXPZym-H@8_+0f-T@{v`gjqv0$W^#} z_0-l1K19I4ALB`?2XM5Vj(13if6Wl? z!xszZCQ5PF z;D?!<)eZ$OzVMSCmN<0Dfv$*2AR1X^%BuhME~S6!{Dq~`IIa6^;mS2=Q}1|#Ps>#eC9@_CtV{9GvgKyljDrDJG6ca)4IA0WpHKAGOwlF{ z=}q@c4mOWP1yu<9T1}NdpWp}Ho)Nxp^7S=bWK(h$ES{C{MjrQmNmv7SS;vT=Qghv} z<#jK3NgZ4K=`hib>2L>~wrVfU?Z%6xfd`8Op8}rmztOR?K)_xdDfwr^^zB*H18D4i Vy(jNDeM`F+aCUTK7Hwn4{2TB>%iI6} delta 2370 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