Commit e9bc0b2bb2d19d5607990d2e3eb3210dbbbc0730

Authored by Mumfrey
0 parents

Example mod for 1.9

.gitignore 0 → 100644
  1 +++ a/.gitignore
  1 +# Build #
  2 +#########
  3 +MANIFEST.MF
  4 +dependency-reduced-pom.xml
  5 +
  6 +# Compiled #
  7 +############
  8 +bin
  9 +build
  10 +dist
  11 +lib
  12 +out
  13 +run
  14 +target
  15 +*.com
  16 +*.class
  17 +*.dll
  18 +*.exe
  19 +*.o
  20 +*.so
  21 +
  22 +# Databases #
  23 +#############
  24 +*.db
  25 +*.sql
  26 +*.sqlite
  27 +
  28 +# Packages #
  29 +############
  30 +*.7z
  31 +*.dmg
  32 +*.gz
  33 +*.iso
  34 +*.rar
  35 +*.tar
  36 +*.zip
  37 +
  38 +# Repository #
  39 +##############
  40 +.git
  41 +
  42 +# Logging #
  43 +###########
  44 +/logs
  45 +*.log
  46 +
  47 +# Misc #
  48 +########
  49 +*.bak
  50 +
  51 +# System #
  52 +##########
  53 +.DS_Store
  54 +ehthumbs.db
  55 +Thumbs.db
  56 +*.bat
  57 +*.sh
  58 +
  59 +# Project #
  60 +###########
  61 +.checkstyle
  62 +.classpath
  63 +.externalToolBuilders
  64 +.gradle
  65 +.nb-gradle
  66 +.idea
  67 +.project
  68 +.settings
  69 +eclipse
  70 +nbproject
  71 +atlassian-ide-plugin.xml
  72 +build.xml
  73 +nb-configuration.xml
  74 +*.iml
  75 +*.ipr
  76 +*.iws
  77 +*.launch
  78 +*.number
  79 +
... ...
build.gradle 0 → 100644
  1 +++ a/build.gradle
  1 +buildscript {
  2 + repositories {
  3 + mavenLocal()
  4 + mavenCentral()
  5 + maven {
  6 + name = "sonatype"
  7 + url = "https://oss.sonatype.org/content/repositories/snapshots/"
  8 + }
  9 + maven {
  10 + name = "forge"
  11 + url = "http://files.minecraftforge.net/maven"
  12 + }
  13 + maven {
  14 + name = 'sponge'
  15 + url = 'http://repo.spongepowered.org/maven'
  16 + }
  17 + }
  18 + dependencies {
  19 + classpath 'net.minecraftforge.gradle:ForgeGradle:2.1-SNAPSHOT'
  20 + classpath 'org.spongepowered:mixingradle:0.4-SNAPSHOT'
  21 + }
  22 +}
  23 +
  24 +apply plugin: 'net.minecraftforge.gradle.liteloader'
  25 +apply plugin: 'org.spongepowered.mixin'
  26 +
  27 +version = "0.1"
  28 +group = "com.mumfrey.examplemod" // http://maven.apache.org/guides/mini/guide-naming-conventions.html
  29 +archivesBaseName = "examplemod"
  30 +
  31 +minecraft {
  32 + version = "1.9"
  33 + mappings = "snapshot_20160410"
  34 + runDir = "run"
  35 +}
  36 +
  37 +sourceSets {
  38 + main {
  39 + // Refmap declaration must match the refmap name specified in the json config
  40 + refMap = "mixins.example.refmap.json"
  41 + }
  42 +}
  43 +
  44 +mixin {
  45 + defaultObfuscationEnv notch
  46 +}
  47 +
  48 +litemod {
  49 + json {
  50 + name = "test"
  51 + mcversion = "1.9.0"
  52 + mixinConfigs += "mixins.example.json"
  53 + }
  54 +}
  55 +
  56 +jar {
  57 + from litemod.outputs
  58 +}
... ...
src/main/java/com/examplemod/Clock.java 0 → 100644
  1 +++ a/src/main/java/com/examplemod/Clock.java
  1 +package com.examplemod;
  2 +
  3 +import static com.mumfrey.liteloader.gl.GL.*;
  4 +import static net.minecraft.client.renderer.vertex.DefaultVertexFormats.*;
  5 +
  6 +import net.minecraft.client.Minecraft;
  7 +import net.minecraft.client.renderer.Tessellator;
  8 +import net.minecraft.client.renderer.VertexBuffer;
  9 +import net.minecraft.util.ResourceLocation;
  10 +import org.lwjgl.util.ReadableColor;
  11 +
  12 +import java.util.Calendar;
  13 +
  14 +/**
  15 + * Simple implementation of an analogue clock to demonstrate how to LiteLoader all the things
  16 + *
  17 + * @author Adam Mummery-Smith
  18 + */
  19 +public class Clock
  20 +{
  21 + /**
  22 + * This is the clock face resource, you need to create a resource location for any assets that you
  23 + * wish to use. It is best to make these static to avoid new instances being created for every instance
  24 + * of the referencing object, this also means they will only be garbage collected when the class is
  25 + * garbage collected or when no instances of the class are left.
  26 + *
  27 + * The first parameter for the resource location is the "domain" and this should normally be your mod's
  28 + * name. The domain MUST be lower case! The second is the resource "path" and represents the path to the
  29 + * resource within the domain. It is convention that the path always start with the resource type, such
  30 + * as "textures" in this case.
  31 + *
  32 + * Resources are always stored in a path of the form "assets/{domain}/{path}" which makes the appropriate
  33 + * path to the CLOCKFACE resource: "/assets/example/textures/clock/face.png"
  34 + */
  35 + private static final ResourceLocation CLOCKFACE = new ResourceLocation("example", "textures/clock/face.png");
  36 +
  37 + /**
  38 + * Angles for the hands
  39 + */
  40 + private float smallHandAngle, largeHandAngle, secondHandAngle;
  41 +
  42 + /**
  43 + * Sizes for each of the hands
  44 + */
  45 + private float smallHandSize, largeHandSize, secondHandSize;
  46 +
  47 + /**
  48 + * Width of the hands
  49 + */
  50 + private float handWidth = 1.0F;
  51 +
  52 + /**
  53 + * Colours for each of the hands
  54 + */
  55 + private ReadableColor smallHandColour, largeHandColour, secondHandColour;
  56 +
  57 + /**
  58 + * Size and position for the clock
  59 + */
  60 + private int xPos, yPos, size;
  61 +
  62 + /**
  63 + * Whether the clock is currently visible
  64 + */
  65 + private boolean visible = true;
  66 +
  67 + /**
  68 + * @param xPos X position for the clock
  69 + * @param yPos Y position for the clock
  70 + */
  71 + public Clock(int xPos, int yPos)
  72 + {
  73 + this.setPosition(xPos, yPos);
  74 + this.setSize(64);
  75 +
  76 + this.largeHandColour = ReadableColor.WHITE;
  77 + this.smallHandColour = ReadableColor.GREY;
  78 + this.secondHandColour = ReadableColor.ORANGE;
  79 + }
  80 +
  81 + /**
  82 + * @param xPos
  83 + * @param yPos
  84 + */
  85 + public void setPosition(int xPos, int yPos)
  86 + {
  87 + this.xPos = Math.max(0, xPos);
  88 + this.yPos = Math.max(0, yPos);
  89 + }
  90 +
  91 + /**
  92 + * Set the size of the clock
  93 + *
  94 + * @param size new size (min is 32)
  95 + */
  96 + public void setSize(int size)
  97 + {
  98 + this.size = Math.max(32, size);
  99 +
  100 + this.smallHandSize = this.size * 0.25F;
  101 + this.largeHandSize = this.size * 0.38F;
  102 + this.secondHandSize = this.size * 0.35F;
  103 +
  104 + this.handWidth = this.size / 64.0F;
  105 + }
  106 +
  107 + /**
  108 + * Get the current size
  109 + */
  110 + public int getSize()
  111 + {
  112 + return this.size;
  113 + }
  114 +
  115 + /**
  116 + * Get whether the clock is currently visible
  117 + */
  118 + public boolean isVisible()
  119 + {
  120 + return this.visible;
  121 + }
  122 +
  123 + /**
  124 + * Set whether the clock should be visible
  125 + *
  126 + * @param visible new visibility setting
  127 + */
  128 + public void setVisible(boolean visible)
  129 + {
  130 + this.visible = visible;
  131 + }
  132 +
  133 + /**
  134 + * Render the clock at its current position, unless hidden
  135 + *
  136 + * @param minecraft Minecraft game instance
  137 + */
  138 + public void render(Minecraft minecraft)
  139 + {
  140 + if (this.isVisible())
  141 + {
  142 + // First, update the hand angles
  143 + this.calculateAngles();
  144 +
  145 + // Then render the actual clock
  146 + this.renderClock(minecraft);
  147 + }
  148 + }
  149 +
  150 + /**
  151 + * Gets the current time and calculates the angles for the clock hands
  152 + */
  153 + private void calculateAngles()
  154 + {
  155 + Calendar calendar = Calendar.getInstance();
  156 +
  157 + int hour = calendar.get(Calendar.HOUR);
  158 + int minute = calendar.get(Calendar.MINUTE);
  159 + int second = calendar.get(Calendar.SECOND);
  160 +
  161 + this.smallHandAngle = 360.0F * (0.0833F * hour + 0.00138F * minute);
  162 + this.largeHandAngle = 360.0F * (0.0166F * minute);
  163 + this.secondHandAngle = 360.0F * (0.0166F * second);
  164 + }
  165 +
  166 + /**
  167 + * Renders the clock
  168 + *
  169 + * @param minecraft Minecraft game instance
  170 + */
  171 + private void renderClock(Minecraft minecraft)
  172 + {
  173 + // Render the face
  174 + this.renderClockFace(minecraft);
  175 +
  176 + // Render each of the hands
  177 + this.renderClockHand(this.largeHandAngle, this.largeHandSize, this.handWidth * 1.2F, this.largeHandColour);
  178 + this.renderClockHand(this.smallHandAngle, this.smallHandSize, this.handWidth * 2.0F, this.smallHandColour);
  179 + this.renderClockHand(this.secondHandAngle, this.secondHandSize, this.handWidth * 1.2F, this.secondHandColour);
  180 + }
  181 +
  182 + /**
  183 + * Renders the clock face texture using the texture resource
  184 + *
  185 + * @param minecraft Minecraft game instance
  186 + */
  187 + private void renderClockFace(Minecraft minecraft)
  188 + {
  189 + // Bind the texture resource
  190 + minecraft.getTextureManager().bindTexture(Clock.CLOCKFACE);
  191 +
  192 + // Draw a rectangle using the currently bound texture
  193 + glDrawTexturedRect(this.xPos, this.yPos, this.size, this.size, 1, 1, 511, 511);
  194 + }
  195 +
  196 + /**
  197 + * Render one of the hands
  198 + */
  199 + private void renderClockHand(float angle, float length, float width, ReadableColor colour)
  200 + {
  201 + // Push the current transform onto the stack
  202 + glPushMatrix();
  203 +
  204 + // Transform to the mid point of the clock
  205 + glTranslatef(this.xPos + (this.size / 2), this.yPos + (this.size / 2), 0);
  206 +
  207 + // and rotate by the hand angle
  208 + glRotatef(angle, 0.0F, 0.0F, 1.0F);
  209 +
  210 + // then draw the hand (straight up of course)
  211 + glDrawRect(width * -0.5F, length * 0.2F, width * 0.5F, -length, colour);
  212 +
  213 + // and finally restore the current transform
  214 + glPopMatrix();
  215 + }
  216 +
  217 + /**
  218 + * Draw a rectangle using the currently bound texture
  219 + */
  220 + private static void glDrawTexturedRect(int x, int y, int width, int height, int u, int v, int u2, int v2)
  221 + {
  222 + // Set the appropriate OpenGL modes
  223 + glDisableLighting();
  224 + glDisableBlend();
  225 + glAlphaFunc(GL_GREATER, 0.01F);
  226 + glEnableTexture2D();
  227 + glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
  228 +
  229 + float texMapScale = 0.001953125F; // 512px
  230 +
  231 + // We use the tessellator rather than drawing individual quads because it uses vertex arrays to
  232 + // draw the quads more efficiently.
  233 + Tessellator tessellator = Tessellator.getInstance();
  234 + VertexBuffer worldRenderer = tessellator.getBuffer();
  235 + worldRenderer.begin(GL_QUADS, POSITION_TEX);
  236 + worldRenderer.pos(x + 0, y + height, 0).tex(u * texMapScale, v2 * texMapScale).endVertex();
  237 + worldRenderer.pos(x + width, y + height, 0).tex(u2 * texMapScale, v2 * texMapScale).endVertex();
  238 + worldRenderer.pos(x + width, y + 0, 0).tex(u2 * texMapScale, v * texMapScale).endVertex();
  239 + worldRenderer.pos(x + 0, y + 0, 0).tex(u * texMapScale, v * texMapScale).endVertex();
  240 + tessellator.draw();
  241 + }
  242 +
  243 + /**
  244 + * Draw an opaque rectangle
  245 + */
  246 + private static void glDrawRect(float x1, float y1, float x2, float y2, ReadableColor colour)
  247 + {
  248 + // Set GL modes
  249 + glDisableBlend();
  250 + glDisableTexture2D();
  251 + glDisableCulling();
  252 + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  253 + glColor4f(colour.getRed(), colour.getGreen(), colour.getBlue(), 1.0F);
  254 +
  255 + // Draw the quad
  256 + Tessellator tessellator = Tessellator.getInstance();
  257 + VertexBuffer worldRenderer = tessellator.getBuffer();
  258 + worldRenderer.begin(GL_QUADS, POSITION);
  259 + worldRenderer.pos(x1, y2, 0).endVertex();
  260 + worldRenderer.pos(x2, y2, 0).endVertex();
  261 + worldRenderer.pos(x2, y1, 0).endVertex();
  262 + worldRenderer.pos(x1, y1, 0).endVertex();
  263 + tessellator.draw();
  264 +
  265 + // Restore GL modes
  266 + glEnableCulling();
  267 + glEnableTexture2D();
  268 + }
  269 +}
... ...
src/main/java/com/examplemod/LiteModExample.java 0 → 100644
  1 +++ a/src/main/java/com/examplemod/LiteModExample.java
  1 +package com.examplemod;
  2 +
  3 +import com.google.gson.annotations.Expose;
  4 +import com.google.gson.annotations.SerializedName;
  5 +import com.mumfrey.liteloader.PreRenderListener;
  6 +import com.mumfrey.liteloader.Tickable;
  7 +import com.mumfrey.liteloader.core.LiteLoader;
  8 +import com.mumfrey.liteloader.modconfig.ConfigStrategy;
  9 +import com.mumfrey.liteloader.modconfig.ExposableOptions;
  10 +import net.minecraft.client.Minecraft;
  11 +import net.minecraft.client.renderer.RenderGlobal;
  12 +import net.minecraft.client.settings.KeyBinding;
  13 +import org.lwjgl.input.Keyboard;
  14 +
  15 +import java.io.File;
  16 +
  17 +/**
  18 + * This is a very simple example LiteMod, it draws an analogue clock on the minecraft HUD using
  19 + * a traditional onTick hook supplied by LiteLoader's "Tickable" interface.
  20 + *
  21 + * @author Adam Mummery-Smith
  22 + */
  23 +@ExposableOptions(strategy = ConfigStrategy.Versioned, filename="examplemod.json")
  24 +public class LiteModExample implements Tickable, PreRenderListener
  25 +{
  26 + /**
  27 + * This is our instance of Clock which we will draw every tick
  28 + */
  29 + private Clock clock = new Clock(10, 10);
  30 +
  31 + /**
  32 + * This is a keybinding that we will register with the game and use to toggle the clock
  33 + *
  34 + * Notice that we specify the key name as an *unlocalised* string. The localisation is provided from the included resource file
  35 + */
  36 + private static KeyBinding clockKeyBinding = new KeyBinding("key.clock.toggle", Keyboard.KEY_F12, "key.categories.litemods");
  37 +
  38 + @Expose
  39 + @SerializedName("clock_size")
  40 + private int clockSize = 64;
  41 +
  42 + @Expose
  43 + @SerializedName("clock_visible")
  44 + private boolean clockVisible = true;
  45 +
  46 + /**
  47 + * Default constructor. All LiteMods must have a default constructor. In general you should do very little
  48 + * in the mod constructor EXCEPT for initialising any non-game-interfacing components or performing
  49 + * sanity checking prior to initialisation
  50 + */
  51 + public LiteModExample()
  52 + {
  53 + }
  54 +
  55 + /**
  56 + * getName() should be used to return the display name of your mod and MUST NOT return null
  57 + *
  58 + * @see com.mumfrey.liteloader.LiteMod#getName()
  59 + */
  60 + @Override
  61 + public String getName()
  62 + {
  63 + return "Example Mod";
  64 + }
  65 +
  66 + /**
  67 + * getVersion() should return the same version string present in the mod metadata, although this is
  68 + * not a strict requirement.
  69 + *
  70 + * @see com.mumfrey.liteloader.LiteMod#getVersion()
  71 + */
  72 + @Override
  73 + public String getVersion()
  74 + {
  75 + return "0.0.0";
  76 + }
  77 +
  78 + /**
  79 + * init() is called very early in the initialisation cycle, before the game is fully initialised, this
  80 + * means that it is important that your mod does not interact with the game in any way at this point.
  81 + *
  82 + * @see com.mumfrey.liteloader.LiteMod#init(java.io.File)
  83 + */
  84 + @Override
  85 + public void init(File configPath)
  86 + {
  87 + // The key binding declared above won't do anything unless we register it, ModUtilties provides
  88 + // a convenience method for this
  89 + LiteLoader.getInput().registerKeyBinding(LiteModExample.clockKeyBinding);
  90 +
  91 + this.clock.setSize(this.clockSize);
  92 + this.clock.setVisible(this.clockVisible);
  93 + }
  94 +
  95 + /**
  96 + * upgradeSettings is used to notify a mod that its version-specific settings are being migrated
  97 + *
  98 + * @see com.mumfrey.liteloader.LiteMod#upgradeSettings(java.lang.String, java.io.File, java.io.File)
  99 + */
  100 + @Override
  101 + public void upgradeSettings(String version, File configPath, File oldConfigPath)
  102 + {
  103 + }
  104 +
  105 + @Override
  106 + public void onTick(Minecraft minecraft, float partialTicks, boolean inGame, boolean clock)
  107 + {
  108 + // The three checks here are critical to ensure that we only draw the clock as part of the "HUD"
  109 + // and don't draw it over active GUI's or other elements
  110 + if (inGame && minecraft.currentScreen == null && Minecraft.isGuiEnabled())
  111 + {
  112 + if (LiteModExample.clockKeyBinding.isPressed())
  113 + {
  114 + if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT))
  115 + {
  116 + this.clockSize = (this.clockSize << 1) & 0x1FF;
  117 + this.clock.setSize(this.clockSize);
  118 + this.clockSize = this.clock.getSize();
  119 + }
  120 + else
  121 + {
  122 + this.clock.setVisible(!this.clock.isVisible());
  123 + this.clockVisible = this.clock.isVisible();
  124 + }
  125 +
  126 + // Our @Expose annotations control what properties get saved, this tells liteloader to
  127 + // actually write the properties to disk
  128 + LiteLoader.getInstance().writeConfig(this);
  129 + }
  130 +
  131 + // Render the clock
  132 + this.clock.render(minecraft);
  133 + }
  134 + }
  135 +
  136 + @Override
  137 + public void onRenderWorld(float partialTicks)
  138 + {
  139 +// System.err.printf(">> onRenderWorld!\n");
  140 + }
  141 +
  142 + @Override
  143 + public void onSetupCameraTransform(float partialTicks, int pass, long timeSlice)
  144 + {
  145 +// System.err.printf(">> onSetupCameraTransform %s, %d, %d!\n", partialTicks, pass, timeSlice);
  146 + }
  147 +
  148 + @Override
  149 + public void onRenderSky(float partialTicks, int pass)
  150 + {
  151 +// System.err.printf(">> onRenderSky %s, %d!\n", partialTicks, pass);
  152 + }
  153 +
  154 + @Override
  155 + public void onRenderClouds(float partialTicks, int pass, RenderGlobal renderGlobal)
  156 + {
  157 +// System.err.printf(">> onRenderClouds %s, %d!\n", partialTicks, pass);
  158 + }
  159 +
  160 + @Override
  161 + public void onRenderTerrain(float partialTicks, int pass)
  162 + {
  163 +// System.err.printf(">> onRenderTerrain %s, %d!\n", partialTicks, pass);
  164 + }
  165 +}
... ...
src/main/java/com/examplemod/mixin/MixinGuiMainMenu.java 0 → 100644
  1 +++ a/src/main/java/com/examplemod/mixin/MixinGuiMainMenu.java
  1 +/*
  2 + * This file is part of Sponge, licensed under the MIT License (MIT).
  3 + *
  4 + * Copyright (c) SpongePowered <https://www.spongepowered.org>
  5 + * Copyright (c) contributors
  6 + *
  7 + * Permission is hereby granted, free of charge, to any person obtaining a copy
  8 + * of this software and associated documentation files (the "Software"), to deal
  9 + * in the Software without restriction, including without limitation the rights
  10 + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11 + * copies of the Software, and to permit persons to whom the Software is
  12 + * furnished to do so, subject to the following conditions:
  13 + *
  14 + * The above copyright notice and this permission notice shall be included in
  15 + * all copies or substantial portions of the Software.
  16 + *
  17 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20 + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22 + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23 + * THE SOFTWARE.
  24 + */
  25 +package com.examplemod.mixin;
  26 +
  27 +import net.minecraft.client.gui.GuiMainMenu;
  28 +import net.minecraft.client.gui.GuiScreen;
  29 +import org.spongepowered.asm.mixin.Mixin;
  30 +import org.spongepowered.asm.mixin.Shadow;
  31 +import org.spongepowered.asm.mixin.injection.At;
  32 +import org.spongepowered.asm.mixin.injection.Inject;
  33 +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
  34 +
  35 +@Mixin(GuiMainMenu.class)
  36 +public abstract class MixinGuiMainMenu extends GuiScreen
  37 +{
  38 + @Shadow private int panoramaTimer;
  39 +
  40 + @Inject(method = "updateScreen()V", at = @At("HEAD"))
  41 + private void onUpdateScreen(CallbackInfo ci)
  42 + {
  43 + this.panoramaTimer += 4;
  44 + }
  45 +}
... ...
src/main/resources/assets/example/lang/en_US.lang 0 → 100644
  1 +++ a/src/main/resources/assets/example/lang/en_US.lang
  1 +
  2 +key.clock.toggle=Toggle Clock
0 3 \ No newline at end of file
... ...
src/main/resources/assets/example/textures/clock/face.png 0 → 100644

22.4 KB

src/main/resources/litemod.json 0 → 100644
  1 +++ a/src/main/resources/litemod.json
  1 +{
  2 + "name": "example",
  3 + "version": 0.0,
  4 + "mcversion": "1.9",
  5 + "mixinConfigs": [
  6 + "mixins.example.json"
  7 + ]
  8 +}
0 9 \ No newline at end of file
... ...
src/main/resources/mixins.example.json 0 → 100644
  1 +++ a/src/main/resources/mixins.example.json
  1 +{
  2 + "required": true,
  3 + "minVersion": "0.5.3",
  4 + "package": "com.examplemod.mixin",
  5 + "refmap": "mixins.example.refmap.json",
  6 + "mixins": [
  7 + "MixinGuiMainMenu"
  8 + ],
  9 + "injectors": {
  10 + "defaultRequire": 1
  11 + }
  12 +}
0 13 \ No newline at end of file
... ...