From 117589b17b834c26479aed2a3ed87a1e3694a085 Mon Sep 17 00:00:00 2001 From: Brychan Dempsey Date: Sat, 12 Jun 2021 15:53:30 +1200 Subject: [PATCH] Got example 2 working Player input; and a different way of setting up the renderer --- demo2/pom.xml | 2 +- demo2/sprite.png | Bin 0 -> 1447 bytes .../nz/ac/massey/javaecs/examples/App.java | 111 +++++++++++++++++- .../javaecs/examples/Components/Control.java | 2 +- .../javaecs/examples/Components/Input.java | 9 ++ .../javaecs/examples/Components/Sprite.java | 10 +- .../ac/massey/javaecs/examples/JFXView.java | 28 +++++ .../javaecs/examples/RenderStarter.java | 16 +++ .../examples/Systems/ControlSystem.java | 70 ++++++++++- .../examples/Systems/PlayerSystem.java | 59 ++++++++++ .../examples/Systems/RenderSystem.java | 76 +++++------- demo2/src/sprite.png | Bin 0 -> 1447 bytes 12 files changed, 322 insertions(+), 61 deletions(-) create mode 100644 demo2/sprite.png create mode 100644 demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Input.java create mode 100644 demo2/src/main/java/nz/ac/massey/javaecs/examples/JFXView.java create mode 100644 demo2/src/main/java/nz/ac/massey/javaecs/examples/RenderStarter.java create mode 100644 demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/PlayerSystem.java create mode 100644 demo2/src/sprite.png diff --git a/demo2/pom.xml b/demo2/pom.xml index 5a7b326..72781cc 100644 --- a/demo2/pom.xml +++ b/demo2/pom.xml @@ -38,7 +38,7 @@ nz.ac.massey.javaecs javaecs - 0.9.2-PRERELEASE + 0.9.9-RELEASE_CANDIDATE diff --git a/demo2/sprite.png b/demo2/sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..b41518be51f27435a48c362d8e60696884b55772 GIT binary patch literal 1447 zcmeAS@N?(olHy`uVBq!ia0vp^^MQCD2NRH7(&?oFq!^2X+?^QKos)S9a~60+7BevL9R^{>JY5_^D&pQ=xj1Q#fq?VHKmYynS3I#w z6<4#~d>}%%MDDJfz}mHczl!d=v~Kg3*E(;lymz02F*oOaziyak({O%y(B0oZ_Ab74 z-_8E|!<~Ao>iBc-G?!lqfA5{~pl;9lOsR@~Yc4pr+Tx8)x$|S02f=2vxeSw_J3>j)x(hxBglRna|ORoYQ%< zL?XoD5g)T;)Z3yc_MOsxsY~t#pSW9~rTliD)4R4{$;|IMi_GU_Ma*ogHJKCU{HraP z7btWIDCDs_ed4}{A=Zpa5w>35%L{JfmVTCmHGX-$DM*zF6-{{Lj5qY z^Q+06EamL>Mxmo+<~$)2Hyw89@zR-grC?Rp6j`<;m)my@E;#SwW43iV84-GTrQ9de zIbN4?%MUwnp0wgo$!twdlx34TaV2qqS8c6y+UkjEM|`Hn*j+PA+Qw(;cBVzsEu~U9 zO?yJZ5ue!^USSqV-y-)^AMu%&7a+Ft@Q=DZ`R#?Qmm(vd!{T?v@zj;@h-Vere2Y0| z7n8rufu%vGTGEam-|^k9uD++5HMHy8CPX@_dU^BL--`Iu(EW{U?+#kO(*8I9d6v!f z){FOFa@ri~d)^*+%kbS!7ONn~D+ zyj41_Z0^$7^mFn)OJ&*mKQ1V^{v6b=EahE6^1{xv~DHZB$*xN z73aI}Y;4_=zQgb^FZWaXHb1vB`wqXA%o7R!ZM-7s$Q#o?X>HO|MgOsRNf=fO+s}LG z<{Nr>^KZe;@(%@#?`W)uD+$|o_=n6q)5E<%U9D-Ct$WrV5Ki9_=)UyGn~L_2ODn$c zm}_ZFo_e)!#@*)fGX>6Zy$f_okGx6SlhY=BNXunT;y1xPMQzfno_s!Q@LT7e?O|Tq zLnrwR;u=|u?+7fGdd={7X@}8aUdgVvn^wslW8yG5%*$I0V%O4>H1WH&fhG%tN(b|TL!^f&*-)I-x_lJ;nz6m1wGiWXD9fPmNc XR$HE&;;jN;;mY9Y>gTe~DWM4f$T`Sc literal 0 HcmV?d00001 diff --git a/demo2/src/main/java/nz/ac/massey/javaecs/examples/App.java b/demo2/src/main/java/nz/ac/massey/javaecs/examples/App.java index ce2c849..98fa768 100644 --- a/demo2/src/main/java/nz/ac/massey/javaecs/examples/App.java +++ b/demo2/src/main/java/nz/ac/massey/javaecs/examples/App.java @@ -1,22 +1,121 @@ package nz.ac.massey.javaecs.examples; +import java.io.FileInputStream; +import java.util.concurrent.Semaphore; + +import javafx.scene.Group; +import javafx.scene.image.Image; +import javafx.stage.Stage; import nz.ac.massey.javaecs.Engine; +import nz.ac.massey.javaecs.Entity; +import nz.ac.massey.javaecs.examples.Components.Control; +import nz.ac.massey.javaecs.examples.Components.Input; +import nz.ac.massey.javaecs.examples.Components.Render; +import nz.ac.massey.javaecs.examples.Components.Sprite; +import nz.ac.massey.javaecs.examples.Components.Vec2D; +import nz.ac.massey.javaecs.examples.Systems.ControlSystem; +import nz.ac.massey.javaecs.examples.Systems.PlayerSystem; +import nz.ac.massey.javaecs.examples.Systems.RenderSystem; /** * Hello world! */ -public final class App { - private App() { +public class App { + static App thisApp; + public App() { + thisApp = this; } - + static Engine gameEngine; + public static Stage worldStage; + public static Group worldGroup; + static Integer syncManager = 0; + static Semaphore rendererFinished = new Semaphore(0); /** * Says hello to the world. * @param args The arguments of the program. */ - public static void main(String[] args) { - Engine gameEngine = new Engine(); + public static void main(String[] args) throws Exception{ + /************************************ + ** Initialise engine ** + *************************************/ + gameEngine = new Engine(16384); + + /************************************ + ** Register game components ** + *************************************/ + gameEngine.registerComponent(Control.class); + gameEngine.registerComponent(Render.class); + gameEngine.registerComponent(Sprite.class); + gameEngine.registerComponent(Vec2D.class); + gameEngine.registerComponent(Input.class); + + + /************************************ + ** Register and initialise systems ** + *************************************/ + ControlSystem controlSystem = new ControlSystem(gameEngine); + gameEngine.registerSystem(ControlSystem.class, controlSystem); + + RenderSystem renderSystem = new RenderSystem(gameEngine, 10.); + gameEngine.registerSystem(RenderSystem.class, renderSystem); + + PlayerSystem playerSystem = new PlayerSystem(gameEngine); + gameEngine.registerSystem(PlayerSystem.class, playerSystem); + + + + /************************************ + ** Create known entities ** + *************************************/ + // Input controller - gets player input + Entity inputEntity = gameEngine.createEntity(); + gameEngine.addComponent(inputEntity, Input.class, new Input()); + + Entity playerEntity = gameEngine.createEntity(); + gameEngine.addComponent(playerEntity, Vec2D.class, new Vec2D(25,25)); + gameEngine.addComponent(playerEntity, Render.class, new Render(Sprite.class)); + gameEngine.addComponent(playerEntity, Control.class, new Render(Control.class)); + // Load the sprite + System.err.println(System.getProperty("user.dir")); + Image image = new Image(new FileInputStream("sprite.png")); + gameEngine.addComponent(playerEntity, Sprite.class, new Sprite(image, 50, 50)); + // Push rendering into its own thread (to meet the design principle of a JavaFX program) + Thread renderThread = new Thread(new RenderStarter()); + renderThread.start(); - System.out.println("Hello World!"); + // Use a semaphore to block until the renderer is set-up + rendererFinished.acquire(); + + /************************************ + ** Run init() on the systems ** + *************************************/ + + controlSystem.init(worldStage); + playerSystem.init(inputEntity); + renderSystem.init(worldGroup); + + double dt = 0.001; + double frameRate = 1.0 / 144; // 1 second divided by target number of frames + + boolean exit = false; + long startTime = System.nanoTime(); + int state = 0; + + while (!exit){ + // Run system updates + controlSystem.update(dt); + playerSystem.update(dt); + renderSystem.update(dt); + + dt = (System.nanoTime() - startTime) / 1e9; // convert nanoseconds to seconds + startTime = System.nanoTime(); + if (dt < frameRate){ + Thread.sleep((int)((frameRate-dt)*1000)); + dt = (System.nanoTime() - startTime) / 1e9; + startTime = System.nanoTime(); + } + } + renderThread.join(); // Wait for any render task to exit } } diff --git a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Control.java b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Control.java index 2b51912..433dce4 100644 --- a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Control.java +++ b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Control.java @@ -1,5 +1,5 @@ package nz.ac.massey.javaecs.examples.Components; public class Control { - + public int input = -1; } diff --git a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Input.java b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Input.java new file mode 100644 index 0000000..8e3a099 --- /dev/null +++ b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Input.java @@ -0,0 +1,9 @@ +package nz.ac.massey.javaecs.examples.Components; + +import java.util.concurrent.atomic.AtomicInteger; + +public class Input { + // As this is a user-input- (i.e. timeframe in 10's or 100's of milliseconds) -derived value, the performance impact of + // AtomicInteger is irrelevant compared to its synchronisation ability. + public AtomicInteger input = new AtomicInteger(-1); +} diff --git a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Sprite.java b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Sprite.java index ae81cb6..5dfcbf5 100644 --- a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Sprite.java +++ b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Components/Sprite.java @@ -3,5 +3,13 @@ package nz.ac.massey.javaecs.examples.Components; import javafx.scene.image.Image; public class Sprite { - Image sprite; + public Image sprite; + public Integer height; + public Integer width; + + public Sprite(Image sprite, int height, int width){ + this.sprite = sprite; + this.width = width; + this.height = height; + } } diff --git a/demo2/src/main/java/nz/ac/massey/javaecs/examples/JFXView.java b/demo2/src/main/java/nz/ac/massey/javaecs/examples/JFXView.java new file mode 100644 index 0000000..ffdedf1 --- /dev/null +++ b/demo2/src/main/java/nz/ac/massey/javaecs/examples/JFXView.java @@ -0,0 +1,28 @@ +package nz.ac.massey.javaecs.examples; + + +import javafx.application.Application; +import javafx.scene.Group; +import javafx.scene.Scene; +import javafx.scene.paint.Color; +import javafx.stage.Stage; + +public class JFXView extends Application { + public static Group root; + + @Override + public void start(Stage primaryStage) throws Exception { + primaryStage.setTitle("JavaECS Demo 2"); + App.worldGroup = new Group(); + App.worldStage = primaryStage; + Scene scene = new Scene(App.worldGroup, 1024, 1024, Color.BLACK); + primaryStage.setScene(scene); + primaryStage.show(); + // Notifiy that the renderer is ready + App.rendererFinished.release(); + } + + public static void run(){ + launch(new String[0]); + } +} diff --git a/demo2/src/main/java/nz/ac/massey/javaecs/examples/RenderStarter.java b/demo2/src/main/java/nz/ac/massey/javaecs/examples/RenderStarter.java new file mode 100644 index 0000000..0ae2b8c --- /dev/null +++ b/demo2/src/main/java/nz/ac/massey/javaecs/examples/RenderStarter.java @@ -0,0 +1,16 @@ +package nz.ac.massey.javaecs.examples; + +import javafx.scene.Group; + +// Add a renderer +public class RenderStarter implements Runnable{ + + @Override + public void run() { + JFXView.run(); + } + void updateScene(Group newGroup){ + JFXView.root.getChildren().clear(); + JFXView.root.getChildren().add(newGroup); + } +} \ No newline at end of file diff --git a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/ControlSystem.java b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/ControlSystem.java index 3940907..e042faf 100644 --- a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/ControlSystem.java +++ b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/ControlSystem.java @@ -1,17 +1,81 @@ package nz.ac.massey.javaecs.examples.Systems; -import nz.ac.massey.javaecs.ECSSystem; +import java.util.BitSet; +import javafx.application.Platform; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.stage.Stage; +import nz.ac.massey.javaecs.ECSSystem; +import nz.ac.massey.javaecs.Engine; +import nz.ac.massey.javaecs.Entity; +import nz.ac.massey.javaecs.examples.Components.Control; +import nz.ac.massey.javaecs.examples.Components.Input; +import nz.ac.massey.javaecs.examples.Components.Render; + + +/** + * The control system creates a handler for input in the world stage. + * It then passes input events as they occur to the Input component + * The ControlSystem reads this input every frame update and enacts + * on the player controller + */ public class ControlSystem extends ECSSystem { + Engine gameEngine; + Entity inputEntity; + + public ControlSystem(Engine gamEngine){ + this.gameEngine = gamEngine; + registrationSet = new BitSet(); + registrationSet.set(gameEngine.getComponentIndex(Input.class)); + } + + public void init(Stage world) { + // use a single entity to control inputs, it is present in the world stage + // and is responsible for collecting & passing data + inputEntity = entities.iterator().next(); + + // Add an event handler to the stage. This + Platform.runLater(new Runnable(){ + @Override + public void run() { + world.addEventHandler(KeyEvent.KEY_PRESSED, (key) -> { + if(key.getCode()==KeyCode.W) { + updateKeyPress(0); + System.err.println("W pressed"); + } + else if(key.getCode()==KeyCode.S) { + updateKeyPress(1); + System.err.println("S pressed"); + } + else if(key.getCode()==KeyCode.A) { + updateKeyPress(2); + System.err.println("A pressed"); + } + else if(key.getCode()==KeyCode.D) { + updateKeyPress(3); + System.err.println("D pressed"); + } + }); + } + }); + } @Override - public void init() { + public void update(double dt) { + //Input inputUpdate = (Input)gameEngine.getComponentData(inputEntity, Input.class); // TODO Auto-generated method stub } + // Updates the input's keypressed state + protected void updateKeyPress(int dir){ + Input inputUpdate = (Input)gameEngine.getComponentData(inputEntity, Input.class); + inputUpdate.input.set(dir); + } + @Override - public void update() { + public void init() throws Exception { // TODO Auto-generated method stub } diff --git a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/PlayerSystem.java b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/PlayerSystem.java new file mode 100644 index 0000000..268ce63 --- /dev/null +++ b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/PlayerSystem.java @@ -0,0 +1,59 @@ +package nz.ac.massey.javaecs.examples.Systems; + +import java.util.BitSet; + +import nz.ac.massey.javaecs.ECSSystem; +import nz.ac.massey.javaecs.Engine; +import nz.ac.massey.javaecs.Entity; +import nz.ac.massey.javaecs.examples.Components.Control; +import nz.ac.massey.javaecs.examples.Components.Input; +import nz.ac.massey.javaecs.examples.Components.Sprite; +import nz.ac.massey.javaecs.examples.Components.Vec2D; + +public class PlayerSystem extends ECSSystem{ + Engine gameEngine; + Entity inputEntity; // Entity which recieves event data + + + public PlayerSystem(Engine gameEngine){ + this.gameEngine = gameEngine; + registrationSet = new BitSet(); + registrationSet.set(gameEngine.getComponentIndex(Control.class)); + } + + public void init(Entity inputEntity){ + this.inputEntity = inputEntity; + } + + @Override + public void init() throws Exception { + // TODO Auto-generated method stub + + } + + @Override + public void update(double dt) { + int state = ((Input)gameEngine.getComponentData(inputEntity, Input.class)).input.get(); + // The value of the input may change before control leaves here; sychronisation isn't + // hugely important through this section. So we can just ignore the change + for (Entity entity : entities) { + Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class); + Sprite sprite = (Sprite)gameEngine.getComponentData(entity, Sprite.class); + if (state == 0){ // W + pos.y -= sprite.height/2; + } + else if (state == 1){ // S + pos.y += sprite.height/2; + } + else if (state == 2){ // A + pos.x -= sprite.width/2; + } + else if (state == 3){ // D + pos.x += sprite.width/2; + } + } + // Finally, unset the state + ((Input)gameEngine.getComponentData(inputEntity, Input.class)).input.set(-1); // Unset the state, it has been handled + } + +} diff --git a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/RenderSystem.java b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/RenderSystem.java index bff4614..e08ea69 100644 --- a/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/RenderSystem.java +++ b/demo2/src/main/java/nz/ac/massey/javaecs/examples/Systems/RenderSystem.java @@ -6,19 +6,23 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Group; import javafx.scene.Scene; +import javafx.scene.image.ImageView; import javafx.scene.paint.Color; import javafx.stage.Stage; import nz.ac.massey.javaecs.ECSSystem; import nz.ac.massey.javaecs.Engine; import nz.ac.massey.javaecs.Entity; import nz.ac.massey.javaecs.examples.Components.Control; +import nz.ac.massey.javaecs.examples.Components.Input; import nz.ac.massey.javaecs.examples.Components.Render; import nz.ac.massey.javaecs.examples.Components.Sprite; import nz.ac.massey.javaecs.examples.Components.Vec2D; - +/* + * This render system is different from demo 1, as its main functionality is provided directly by the program, not as an entity. +*/ public class RenderSystem extends ECSSystem { - JFXView jFXView; Engine gameEngine; + Group renderGroup; Thread renderThread; double renderScale; int screenX = 1024; @@ -29,69 +33,43 @@ public class RenderSystem extends ECSSystem { // Set registrations registrationSet = new BitSet(); registrationSet.set(gameEngine.getComponentIndex(Render.class)); - registrationSet.set(gameEngine.getComponentIndex(Vec2D.class)); this.renderScale = renderScale; } - - @Override - public void init() { - // Spawn a new asynchronous thread (won't rejoin the main program flow) - renderThread = new Thread(new RenderStarter()); - renderThread.start(); + public void init(Group renderGroup) throws Exception { + this.renderGroup = renderGroup; } @Override - public void update() { - Group renderScene = new Group(); + public void init() throws Exception { + // TODO Auto-generated method stub + + } + + @Override + public void update(double dt) { + Group frame = new Group(); for (Entity entity : entities) { - if (((Render)gameEngine.getComponentData(entity, Render.class)).renderType == Control.class){ - // Do control updates here - } - else if (((Render)gameEngine.getComponentData(entity, Render.class)).renderType == Sprite.class){ - Vec2D vec2d = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class); + Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class); + if (((Render)gameEngine.getComponentData(entity, Render.class)).renderType == Sprite.class){ + // Render the sprite Sprite sprite = (Sprite)gameEngine.getComponentData(entity, Sprite.class); + ImageView spriteRender = new ImageView(sprite.sprite); + spriteRender.setX(pos.x); + spriteRender.setY(pos.y); + spriteRender.setFitWidth(sprite.width); + spriteRender.setFitHeight(sprite.height); + frame.getChildren().add(spriteRender); } } - // Must be run by the UI thread, not the main thread. // This dispatches the operation run() to the UI. Platform.runLater(new Runnable(){ @Override public void run() { - JFXView.root.getChildren().clear(); - JFXView.root.getChildren().add(renderScene); + renderGroup.getChildren().clear(); + renderGroup.getChildren().add(frame); } }); } - - // Add a renderer - public class RenderStarter implements Runnable{ - - @Override - public void run() { - JFXView.run(); - } - void updateScene(Group newGroup){ - JFXView.root.getChildren().clear(); - JFXView.root.getChildren().add(newGroup); - } - } - - public static class JFXView extends Application { - private static Group root; - - @Override - public void start(Stage primaryStage) throws Exception { - // based on the colorful circles sample at https://docs.oracle.com/javase/8/javafx/get-started-tutorial/animation.htm - root = new Group(); - Scene scene = new Scene(root, 1024, 1024, Color.BLACK); - primaryStage.setScene(scene); - primaryStage.show(); - } - - public static void run(){ - launch(new String[0]); - } - } } diff --git a/demo2/src/sprite.png b/demo2/src/sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..b41518be51f27435a48c362d8e60696884b55772 GIT binary patch literal 1447 zcmeAS@N?(olHy`uVBq!ia0vp^^MQCD2NRH7(&?oFq!^2X+?^QKos)S9a~60+7BevL9R^{>JY5_^D&pQ=xj1Q#fq?VHKmYynS3I#w z6<4#~d>}%%MDDJfz}mHczl!d=v~Kg3*E(;lymz02F*oOaziyak({O%y(B0oZ_Ab74 z-_8E|!<~Ao>iBc-G?!lqfA5{~pl;9lOsR@~Yc4pr+Tx8)x$|S02f=2vxeSw_J3>j)x(hxBglRna|ORoYQ%< zL?XoD5g)T;)Z3yc_MOsxsY~t#pSW9~rTliD)4R4{$;|IMi_GU_Ma*ogHJKCU{HraP z7btWIDCDs_ed4}{A=Zpa5w>35%L{JfmVTCmHGX-$DM*zF6-{{Lj5qY z^Q+06EamL>Mxmo+<~$)2Hyw89@zR-grC?Rp6j`<;m)my@E;#SwW43iV84-GTrQ9de zIbN4?%MUwnp0wgo$!twdlx34TaV2qqS8c6y+UkjEM|`Hn*j+PA+Qw(;cBVzsEu~U9 zO?yJZ5ue!^USSqV-y-)^AMu%&7a+Ft@Q=DZ`R#?Qmm(vd!{T?v@zj;@h-Vere2Y0| z7n8rufu%vGTGEam-|^k9uD++5HMHy8CPX@_dU^BL--`Iu(EW{U?+#kO(*8I9d6v!f z){FOFa@ri~d)^*+%kbS!7ONn~D+ zyj41_Z0^$7^mFn)OJ&*mKQ1V^{v6b=EahE6^1{xv~DHZB$*xN z73aI}Y;4_=zQgb^FZWaXHb1vB`wqXA%o7R!ZM-7s$Q#o?X>HO|MgOsRNf=fO+s}LG z<{Nr>^KZe;@(%@#?`W)uD+$|o_=n6q)5E<%U9D-Ct$WrV5Ki9_=)UyGn~L_2ODn$c zm}_ZFo_e)!#@*)fGX>6Zy$f_okGx6SlhY=BNXunT;y1xPMQzfno_s!Q@LT7e?O|Tq zLnrwR;u=|u?+7fGdd={7X@}8aUdgVvn^wslW8yG5%*$I0V%O4>H1WH&fhG%tN(b|TL!^f&*-)I-x_lJ;nz6m1wGiWXD9fPmNc XR$HE&;;jN;;mY9Y>gTe~DWM4f$T`Sc literal 0 HcmV?d00001