Compare commits

..

No commits in common. "main" and "0.5.1" have entirely different histories.
main ... 0.5.1

22 changed files with 143 additions and 338 deletions

View file

@ -1,12 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"cake.tool": {
"version": "3.0.0",
"commands": [
"dotnet-cake"
]
}
}
}

4
.gitignore vendored
View file

@ -1,6 +1,4 @@
bin/
obj/
/packages/
.idea/
.vs/
.vscode/
.idea

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

View file

@ -1,3 +1,3 @@
ExampleMod.CustomTableUp
loc 0 0 32 32
piv 16 20
piv 16 16

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,8 @@
{
"BuildMode": {
"ExampleMod.CustomTable": "Custom Table"
},
"Clothes": {
"ExampleMod.DarkShirt": "Dark Shirt"
}
}

View file

@ -1,22 +0,0 @@
{
"BuildMode": {
"ExampleMod.CustomTable": "Custom Table",
"ExampleMod.CrossedWallpaper": "Crossed Wallpaper",
"ExampleMod.CustomTile": "Custom Tile"
},
"Clothes": {
"ExampleMod.DarkShirt": "Dark Shirt",
"ExampleMod.PastelPants": "Pastel Pants",
"ExampleMod.PastelShoes": "Pastel Shoes",
"ExampleMod.WeirdHair": "Weird Hair"
},
"Emotions": {
"ExampleMod.GrassSitting": "Comfy Green Ground"
},
"Actions": {
"ExampleMod.SitOnGrass": "Sit on Grass"
},
"Ui": {
"ExampleMod.DarkShirtSpeedOption": "Dark Shirt Speed"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 706 B

23
CustomTable.cs Normal file
View file

@ -0,0 +1,23 @@
using System;
using Microsoft.Xna.Framework;
using TinyLife.Objects;
using TinyLife.World;
namespace ExampleMod {
public class CustomTable : Furniture {
public CustomTable(Guid id, FurnitureType type, int[] colors, Map map, Vector2 pos) : base(id, type, colors, map, pos) {
}
public override void OnAdded() {
base.OnAdded();
ExampleMod.Logger.Info("We were added at " + this.Position);
}
public override void OnRemoved() {
base.OnRemoved();
ExampleMod.Logger.Info("We were removed from " + this.Position);
}
}
}

View file

@ -1,219 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using ExtremelySimpleLogger;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Data;
using MLEM.Data.Content;
using MLEM.Textures;
using MLEM.Ui;
using MLEM.Ui.Elements;
using TinyLife;
using TinyLife.Actions;
using TinyLife.Emotions;
using TinyLife.Mods;
using TinyLife.Objects;
using TinyLife.Utilities;
using TinyLife.World;
using Action = TinyLife.Actions.Action;
namespace ExampleMod;
namespace ExampleMod {
public class ExampleMod : Mod {
public class ExampleMod : Mod {
// the logger that we can use to log info about this mod
public static Logger Logger { get; private set; }
// the logger that we can use to log info about this mod
public static Logger Logger { get; private set; }
public static ExampleOptions Options { get; private set; }
// visual data about this mod
public override string Name => "Example Mod";
public override string Description => "This is the example mod for Tiny Life!";
public static EmotionModifier GrassSittingModifier { get; private set; }
private UniformTextureAtlas customClothes;
// visual data about this mod
public override string Name => "Example Mod";
public override string Description => "This is the example mod for Tiny Life!";
public override TextureRegion Icon => this.uiTextures[new Point(0, 0)];
public override string IssueTrackerUrl => "https://github.com/Ellpeck/TinyLifeExampleMod/issues";
public override string TestedVersionRange => "[0.42.0,0.42.4]";
public override void AddGameContent(GameImpl game) {
// adding a custom furniture item
FurnitureType.Register(new FurnitureType.TypeSettings("ExampleMod.CustomTable", new Point(1, 1), ObjectCategory.Table, 150, ColorScheme.SimpleWood) {
Construct = (i, t, c, m, p) => new CustomTable(i, t, c, m, p)
});
private Dictionary<Point, TextureRegion> customTops;
private Dictionary<Point, TextureRegion> customHairs;
private Dictionary<Point, TextureRegion> customBottoms;
private Dictionary<Point, TextureRegion> uiTextures;
private Dictionary<Point, TextureRegion> wallpaperTextures;
private Dictionary<Point, TextureRegion> tileTextures;
public override void Initialize(Logger logger, RawContentManager content, RuntimeTexturePacker texturePacker, ModInfo info) {
ExampleMod.Logger = logger;
ExampleMod.Options = info.LoadOptions(() => new ExampleOptions());
// loads a texture atlas with the given amount of separate texture regions in the x and y axes
// we submit it to the texture packer to increase rendering performance. The callback is invoked once packing is completed
// additionally, we pad all texture regions by 1 pixel, so that rounding errors during rendering don't cause visual artifacts
texturePacker.Add(new UniformTextureAtlas(content.Load<Texture2D>("CustomTops"), 4, 11), r => this.customTops = r, 1, true);
texturePacker.Add(new UniformTextureAtlas(content.Load<Texture2D>("CustomHairs"), 4, 5), r => this.customHairs = r, 1, true);
texturePacker.Add(new UniformTextureAtlas(content.Load<Texture2D>("CustomBottomsShoes"), 8, 6), r => this.customBottoms = r, 1, true);
texturePacker.Add(new UniformTextureAtlas(content.Load<Texture2D>("UiTextures"), 8, 8), r => this.uiTextures = r, 1, true);
texturePacker.Add(new UniformTextureAtlas(content.Load<Texture2D>("Tiles"), 4, 2), r => this.tileTextures = r, 1, true);
// wallpaper textures require special treatment to work with openings, the x and y values are passed to the UniformTextureAtlas constructor
WallMode.ApplyMasks(content.Load<Texture2D>("Wallpapers"), 4, 5, texturePacker, r => this.wallpaperTextures = r);
}
public override void AddGameContent(GameImpl game, ModInfo info) {
// adding a custom furniture item
FurnitureType.Register(new FurnitureType.TypeSettings("ExampleMod.CustomTable", new Point(1, 1), ObjectCategory.Table, 150, ColorScheme.SimpleWood) {
// specify the type that should be constructed when this furniture type is placed
// if this is not specified, the Furniture class is used, which is used for furniture without special animations or data
ConstructedType = typeof(ExampleTable),
// specifying icons for custom clothes and furniture is optional, but using the mod's icon helps users recognize a mod's features
Icon = this.Icon,
// allow chairs and plates to be slotted into and onto the table
ObjectSpots = ObjectSpot.TableSpots(new Point(1, 1)).ToArray()
});
// adding custom clothing
var darkShirt = new Clothes("ExampleMod.DarkShirt", ClothesLayer.Shirt,
// the top left in-world region
// additional regions will be auto-gathered from the atlas according to the rules described in https://docs.tinylifegame.com/articles/creating_textures.html
this.customTops, new Point(0, 0),
// the price
100,
// the clothes item's use cases
ClothesIntention.Everyday | ClothesIntention.Workout,
// the clothes item's style preferences, which influence randomly generated tinies slightly
// neutral style preferences have the same chance to be picked for all tinies, others have a 25% chance for mismatched preferences
StylePreference.Neutral,
// the clothes item's color scheme
// if the item should have multiple layers, multiple color schemes can be supplied here (see docs above)
ColorScheme.WarmDark
) {Icon = this.Icon};
Clothes.Register(darkShirt);
// adding some more custom clothing
Clothes.Register(new Clothes("ExampleMod.PastelPants", ClothesLayer.Pants, this.customBottoms, new Point(4, 0), 100, ClothesIntention.Everyday, StylePreference.Neutral, ColorScheme.Pastel) {Icon = this.Icon});
Clothes.Register(new Clothes("ExampleMod.PastelShoes", ClothesLayer.Shoes, this.customBottoms, new Point(0, 0), 100, ClothesIntention.Everyday, StylePreference.Neutral, ColorScheme.Pastel) {Icon = this.Icon});
Clothes.Register(new Clothes("ExampleMod.WeirdHair", ClothesLayer.Hair, this.customHairs, new Point(0, 0), 0, ClothesIntention.None, StylePreference.Neutral, ColorScheme.Modern) {Icon = this.Icon});
// adding an event subscription to people
MapObject.OnEventsAttachable += o => {
if (o is Person person) {
// changing the walk speed to be doubled if a person is wearing our dark shirt
person.OnGetWalkSpeed += (ref float s) => {
if (person.CurrentOutfit.Clothes.TryGetValue(ClothesLayer.Shirt, out var shirt) && shirt.Type == darkShirt)
s *= ExampleMod.Options.DarkShirtSpeedIncrease;
};
}
};
// adding a simple action: sitting down in the grass, which also gives us a nice emotion modifier
ActionType.Register(new ActionType.TypeSettings("ExampleMod.SitOnGrass", ObjectCategory.Ground, typeof(ExampleGrassSitAction)) {
// we set this action to be executable only on grass tiles, not on other ground
CanExecute = (actionInfo, _) => {
if (!actionInfo.GoalMap.IsInBounds(actionInfo.ActionLocation.ToPoint()))
return CanExecuteResult.Hidden;
var tile = actionInfo.GoalMap.GetTile(actionInfo.ActionLocation.ToPoint(), (int) actionInfo.ActionFloor);
// hidden means the action won't be displayed in the ring menu, Valid means the player (or AI) is able to enqueue and execute it
return tile.Name.StartsWith("Grass") ? CanExecuteResult.Valid : CanExecuteResult.Hidden;
},
Ai = {
// we allow the action to be done even if the solved needs aren't low enough on a person
CanDoRandomly = true,
// the solved needs indicate when the AI should mark this action as important, they don't actually have to match the action's behavior
SolvedNeeds = [NeedType.Energy],
// make people more likely to sit down in the grass if they're uncomfortable
PassivePriority = p => p.Emotion == EmotionType.Uncomfortable ? 150 : 25
},
// since this action doesn't use objects (like chairs etc.), we set a texture to display instead
Texture = this.uiTextures[new Point(1, 0)]
});
// we use this emotion modifier in SitDownOnGrassAction
ExampleMod.GrassSittingModifier = EmotionModifier.Register(
new EmotionModifier("ExampleMod.GrassSitting", this.uiTextures[new Point(1, 0)], EmotionType.Happy));
// adding a custom wallpaper (we're using the top left texture region, which is why we pass 0, 0 as the texture coordinate)
Wallpaper.Register("ExampleMod.CrossedWallpaper", 15, this.wallpaperTextures, new Point(0, 0), ColorScheme.Modern, this.Icon);
// adding a custom tile
Tile.Register("ExampleMod.CustomTile", 8, this.tileTextures, new Point(0, 0), ColorScheme.Bricks, icon: this.Icon);
}
public override IEnumerable<string> GetCustomFurnitureTextures(ModInfo info) {
// tell the game about our custom furniture texture
// this needs to be a path to a data texture atlas, relative to our "Content" directory
// the texture atlas combines the png texture and the .atlas information
// see https://mlem.ellpeck.de/api/MLEM.Data.DataTextureAtlas.html for more info
yield return "CustomFurniture";
}
// this method can be overridden to populate the section in the mod tab of the game's options menu where this mod's options should be displayed
// this mod uses the ModOptions class to manage its options, though that is optional
// in general, options should be stored in the ModInfo.OptionsFile file that is given to the mod by the game
public override void PopulateOptions(Group group, ModInfo info) {
group.AddChild(new Paragraph(Anchor.AutoLeft, 1, _ => $"{Localization.Get(LnCategory.Ui, "ExampleMod.DarkShirtSpeedOption")}: {ExampleMod.Options.DarkShirtSpeedIncrease}"));
group.AddChild(new Slider(Anchor.AutoLeft, new Vector2(1, 10), 5, 5) {
CurrentValue = ExampleMod.Options.DarkShirtSpeedIncrease,
OnValueChanged = (_, v) => ExampleMod.Options.DarkShirtSpeedIncrease = v
});
group.OnRemovedFromUi += _ => info.SaveOptions(ExampleMod.Options);
}
}
// these options are saved and loaded in ExampleMod
public class ExampleOptions {
public float DarkShirtSpeedIncrease = 2;
}
// we use a multi action because we want to walk to the location, and then execute the main sitting part
// see ExampleTable for information on how to store custom action-specific information to disk as well
public class ExampleGrassSitAction : MultiAction {
public ExampleGrassSitAction(ActionType type, ActionInfo info) : base(type, info) {}
protected override IEnumerable<Action> CreateFirstActions() {
// we want to walk to the location clicked, so we use the current action info
yield return ActionType.GoHere.Construct<Action>(this.Info);
}
protected override void AndThenUpdate(GameTime time, TimeSpan passedInGame, float speedMultiplier) {
base.AndThenUpdate(time, passedInGame, speedMultiplier);
// set our person to look like they're sitting on the ground
this.Person.CurrentPose = Pose.SittingGround;
// restore need and lower emotions
this.Person.RestoreNeed(NeedType.Energy, 0.5F, this.Info, speedMultiplier);
this.Person.LowerEmotion(EmotionType.Uncomfortable, 0.0001F, speedMultiplier);
}
protected override CompletionType AndThenIsCompleted() {
// we want to complete our action once 10 minutes of sitting time have passed
return this.CompleteIfTimeUp(TimeSpan.FromMinutes(10));
}
protected override void AndThenOnCompleted(CompletionType type) {
base.AndThenOnCompleted(type);
// this method is called when the action completes in any way, even if it fails
if (type == CompletionType.Completed) {
// once we're finished sitting, we want to get a nice emotion modifier for it
this.Person.AddEmotion(ExampleMod.GrassSittingModifier, 2, TimeSpan.FromHours(1), this.Type);
// adding custom clothing
Clothes.Register(new Clothes("ExampleMod.DarkShirt", ClothesLayer.Shirt,
this.customClothes[0, 0], // the top left in-world region (the rest will be auto-gathered from the atlas)
ColorScheme.WarmDark));
}
public override void Initialize(Logger logger, RawContentManager content, RuntimeTexturePacker texturePacker) {
Logger = logger;
// loads a texture atlas with the given amount of separate texture regions in the x and y axes
// we submit it to the texture packer to increase rendering performance. The callback is invoked once packing is completed
texturePacker.Add(content.Load<Texture2D>("CustomClothes"), r => this.customClothes = new UniformTextureAtlas(r, 4, 6));
}
public override IEnumerable<string> GetCustomFurnitureTextures() {
// tell the game about our custom furniture texture
// this needs to be a path to a data texture atlas, relative to our "Content" directory
// the texture atlas combines the png texture and the .atlas information
// see https://mlem.ellpeck.de/api/MLEM.Data.DataTextureAtlas.html for more info
yield return "CustomFurniture";
}
}
}
// note that having a custom class for a furniture item like this is entirely optional
// but it allows for additional functionalities as displayed in this example
public class ExampleTable : Furniture {
// anything whose base classes have the DataContract attribute automatically gets saved and loaded to and from disk
// this means that you can add custom DataMember members to have them saved and loaded
[DataMember]
public float TestValue;
public ExampleTable(Guid id, FurnitureType type, int[] colors, Map map, Vector2 pos, float floor) : base(id, type, colors, map, pos, floor) {
this.TestValue = Furniture.Random.NextSingle();
}
}
}

View file

@ -1,29 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TinyLifeApi" Version="0.42.4" />
<PackageReference Include="TinyLifeApi" Version="0.5.1" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
<PackageReference Include="Lib.Harmony" Version="2.2.2" />
<PackageReference Include="ExtremelySimpleLogger" Version="1.4.1" />
<PackageReference Include="MLEM.Data" Version="6.3.0" />
<PackageReference Include="MLEM.Extended" Version="6.3.0" />
<PackageReference Include="MLEM.Startup" Version="6.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="DynamicEnums" Version="1.2.0" />
<PackageReference Include="Coroutine" Version="2.1.5" />
<PackageReference Include="ExtremelySimpleLogger" Version="1.2.1" />
<PackageReference Include="MLEM.Data" Version="4.3.0-20" />
<PackageReference Include="MLEM.Extended" Version="4.3.0-20" />
<PackageReference Include="MLEM.Startup" Version="4.3.0-20" />
<PackageReference Include="MonoGame.Extended" Version="3.8.0" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641" />
</ItemGroup>
<ItemGroup>
<Content Include="./Content/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="SteamThumbnail.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Ellpeck
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!-- For preview versions of MLEM, which are sometimes required -->
<add key="Ellpeck" value="https://nuget.ellpeck.de/v3/index.json" />
</packageSources>
</configuration>

View file

@ -1,4 +1,37 @@
# Tiny Life Example Mod
An example mod for my game Tiny Life. Use this template repository to create your own mod!
To learn how to use the example mod repository and create your own mods, check out the [Modding Basics](https://docs.tinylifegame.com/articles/mod_basics.html) documentation.
# Installing mods
Installing a mod is pretty simple:
- Find the `Tiny Life` folder (you can open it from the game's options menu)
- Find the `Mods` folder in there
- If you received the mod you want to install as a `zip` (or any other kind of) archive, extract it first
- Put the mod's `dll` as well as its `Content` folder into the `Mods` folder.
Done! Now just start the game and the mod should automatically load. If there are any errors, they'll be logged in the `Log.txt` file in the `Tiny Life` folder.
# Creating mods
To create a mod, all you have to do is create a repository [from this template](https://github.com/Ellpeck/TinyLifeExampleMod/generate) and open the project contained in it using Visual Studio, Rider or any other kind of C# IDE. The code that is already there contains some examples. Once you're done checking them out, you can just delete them and start fresh.
Since Tiny Life uses early versions of some of my libraries, you will also have to add [my server](https://nuget.ellpeck.de/) to your NuGet config. You can do so using the following command:
```
dotnet nuget add source https://nuget.ellpeck.de/v3/index.json --name "Ellpeck"
```
This repository also contains a little script called `Run.sh` that you can use to automatically build your mod, copy it into the `Mods` directory of your Tiny Life instance and run the game. Just be sure to modify all of the paths first.
The game's API is currently in the process of being documented. This documentation is easily accessible by opening any of the API's files in your IDE. In the future, there will additionally be a web version of the API documentation.
## Distributing mods
To distribute your mod to other people, all you have to do is go into the `bin/Debug/netcoreapp3.0` folder after building and copy your mod's `dll` and the `Content` directory. You can either send them to your friends directly or pack them into an archive first.
## Updating mods
To change the version of Tiny Life that your mod is compiled against, simply go into [the project file](https://github.com/Ellpeck/TinyLifeExampleMod/blob/main/ExampleMod.csproj) and change the `TinyLifeApi` version. Note that some other dependencies might also have been updated, which needs to be [taken into account](https://github.com/Ellpeck/TinyLifeExampleMod#dependency-version-history).
## Where's the source code?
The NuGet package for the Tiny Life API just contains a [reference assembly](https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies) so that people can't just download the game from NuGet and play it. Since most of the public API is documented, you won't have to look at the source code in most cases, anyway.
Technically, you can download the game [from itch](https://ellpeck.itch.io/tiny-life) and then decompile it to see the implementation's code, but due to its license, copying it is not allowed.
## Dependency version history
Since the mod is compiled against the same dependencies as Tiny Life, it also needs to have the same versions of those dependencies for mods to work correctly with the game. Each update to this repository is [tagged](https://github.com/Ellpeck/TinyLifeExampleMod/tags) with the game's version number. If you want to develop for a certain version, just check that tag's [project file](https://github.com/Ellpeck/TinyLifeExampleMod/blob/main/ExampleMod.csproj) to see the required dependency versions.

12
Run.sh Normal file
View file

@ -0,0 +1,12 @@
#!/bin/bash
GAME_DIR="path/to/Tiny Life"
# move to the script directory
cd "$(dirname "$0")"
# build the mod
dotnet build
# copy the mod to the mods folder
cp ./bin/Debug/netcoreapp3.0/* "$LOCALAPPDATA/Tiny Life/Mods" -r
# run the game
cd "$GAME_DIR"
"./Tiny Life.exe"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

View file

@ -1,78 +0,0 @@
using System.Diagnostics;
using System.Linq;
using System.Threading;
var target = Argument("target", "Run");
var config = Argument("configuration", "Release");
var args = Argument("args", "");
var tinyLifeDir = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/Tiny Life";
Task("Clean").Does(() => {
EnsureDirectoryDoesNotExist($"bin/{config}");
EnsureDirectoryDoesNotExist($"{tinyLifeDir}/Mods/_Dev");
});
Task("Build").DoesForEach(GetFiles("**/*.csproj"), p => {
DotNetBuild(p.FullPath, new DotNetBuildSettings {
Configuration = config
});
});
Task("CopyToMods").IsDependentOn("Build").Does(() => {
var dir = $"{tinyLifeDir}/Mods/_Dev";
EnsureDirectoryExists(dir);
var files = GetFiles($"bin/{config}/net*/**/*");
CopyFiles(files, dir, true);
});
Task("Run").IsDependentOn("CopyToMods").Does(() => {
// start the tiny life process
var exeDir = System.IO.File.ReadAllText($"{tinyLifeDir}/GameDir");
var process = Process.Start(new ProcessStartInfo($"{exeDir}/Tiny Life") {
Arguments = $"-v --skip-splash --skip-preloads --debug-saves --ansi {args}",
RedirectStandardOutput = true,
RedirectStandardError = true
});
// make sure the output buffers (which we ignore) don't fill up
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// we wait a bit to make sure the process has generated a new log file
Thread.Sleep(1000);
// attach to the newest log file
var logsDir = $"{tinyLifeDir}/Logs";
var log = System.IO.Directory.EnumerateFiles(logsDir).OrderByDescending(System.IO.File.GetCreationTime).FirstOrDefault();
if (log != null) {
using (var stream = new FileStream(log, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
using (var reader = new StreamReader(stream)) {
var lastPos = 0L;
do {
if (stream.Length > lastPos) {
stream.Seek(lastPos, SeekOrigin.Begin);
string line;
while ((line = reader.ReadLine()) != null)
Information(line);
lastPos = stream.Position;
}
Thread.Sleep(10);
} while (!process.HasExited);
}
}
}
Information($"Tiny Life exited with exit code {process.ExitCode}");
});
Task("Publish").IsDependentOn("Build").DoesForEach(() => GetDirectories($"bin/{config}/net*"), d => {
var dllFile = GetFiles($"{d}/**/*.dll").FirstOrDefault();
if (dllFile == null)
throw new Exception($"Couldn't find built mod in {d}");
var dllName = System.IO.Path.GetFileNameWithoutExtension(dllFile.ToString());
var zipLoc = $"{d.GetParent()}/{dllName}.zip";
Zip(d, zipLoc, GetFiles($"{d}/**/*"));
Information($"Published {dllName} to {zipLoc}");
});
RunTarget(target);