diff --git a/CustomTable.cs b/CustomTable.cs index 655cb56..0d4fbb1 100644 --- a/CustomTable.cs +++ b/CustomTable.cs @@ -4,35 +4,35 @@ using Microsoft.Xna.Framework; using TinyLife.Objects; using TinyLife.World; -namespace ExampleMod { - // 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 CustomTable : Furniture { +namespace ExampleMod; - // 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; +// 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 CustomTable : Furniture { - public CustomTable(Guid id, FurnitureType type, int[] colors, Map map, Vector2 pos) : base(id, type, colors, map, pos) { - this.TestValue = Random.NextSingle(); - } - - 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); - } - - // validate is called when this object is loaded from disk - // returning false causes the object to be marked as invalid and removed - public override bool Validate() { - return base.Validate() && this.TestValue <= 1; - } + // 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 CustomTable(Guid id, FurnitureType type, int[] colors, Map map, Vector2 pos) : base(id, type, colors, map, pos) { + this.TestValue = Random.NextSingle(); } + + 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); + } + + // validate is called when this object is loaded from disk + // returning false causes the object to be marked as invalid and removed + public override bool Validate() { + return base.Validate() && this.TestValue <= 1; + } + } \ No newline at end of file diff --git a/ExampleMod.cs b/ExampleMod.cs index 4564170..46a05de 100644 --- a/ExampleMod.cs +++ b/ExampleMod.cs @@ -13,103 +13,103 @@ using TinyLife.Mods; using TinyLife.Objects; using TinyLife.Utilities; -namespace ExampleMod { - public class ExampleMod : Mod { +namespace ExampleMod; - // the logger that we can use to log info about this mod - public static Logger Logger { get; private set; } +public class ExampleMod : Mod { - public static EmotionModifier GrassSittingModifier { get; private set; } + // the logger that we can use to log info about this mod + public static Logger Logger { 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 override TextureRegion Icon => this.uiTextures[0, 0]; + public static EmotionModifier GrassSittingModifier { get; private set; } - private UniformTextureAtlas customTops; - private UniformTextureAtlas customHairs; - private UniformTextureAtlas customBottoms; - private UniformTextureAtlas uiTextures; + // 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[0, 0]; - 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) { - // 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(CustomTable), - // 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() - }); + private UniformTextureAtlas customTops; + private UniformTextureAtlas customHairs; + private UniformTextureAtlas customBottoms; + private UniformTextureAtlas uiTextures; - // adding custom clothing - var darkShirt = new Clothes("ExampleMod.DarkShirt", ClothesLayer.Shirt, - this.customTops[0, 0], // the top left in-world region (the rest will be auto-gathered from the atlas) - 100, // the price - ClothesIntention.Everyday | ClothesIntention.Workout, // the clothes item's use cases - ColorScheme.WarmDark) {Icon = this.Icon}; - Clothes.Register(darkShirt); - // adding some more custom clothing - Clothes.Register(new Clothes("ExampleMod.PastelPants", ClothesLayer.Pants, this.customBottoms[4, 0], 100, ClothesIntention.Everyday, ColorScheme.Pastel) {Icon = this.Icon}); - Clothes.Register(new Clothes("ExampleMod.PastelShoes", ClothesLayer.Shoes, this.customBottoms[0, 0], 100, ClothesIntention.Everyday, ColorScheme.Pastel) {Icon = this.Icon}); - Clothes.Register(new Clothes("ExampleMod.WeirdHair", ClothesLayer.Hair, this.customHairs[0, 0], 0, ClothesIntention.None, ColorScheme.Modern) {Icon = this.Icon}); + 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) { + // 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(CustomTable), + // 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 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.TryGetValue(ClothesLayer.Shirt, out var shirt) && shirt.Type == darkShirt) - s *= 2; - }; - } - }; + // adding custom clothing + var darkShirt = new Clothes("ExampleMod.DarkShirt", ClothesLayer.Shirt, + this.customTops[0, 0], // the top left in-world region (the rest will be auto-gathered from the atlas) + 100, // the price + ClothesIntention.Everyday | ClothesIntention.Workout, // the clothes item's use cases + ColorScheme.WarmDark) {Icon = this.Icon}; + Clothes.Register(darkShirt); + // adding some more custom clothing + Clothes.Register(new Clothes("ExampleMod.PastelPants", ClothesLayer.Pants, this.customBottoms[4, 0], 100, ClothesIntention.Everyday, ColorScheme.Pastel) {Icon = this.Icon}); + Clothes.Register(new Clothes("ExampleMod.PastelShoes", ClothesLayer.Shoes, this.customBottoms[0, 0], 100, ClothesIntention.Everyday, ColorScheme.Pastel) {Icon = this.Icon}); + Clothes.Register(new Clothes("ExampleMod.WeirdHair", ClothesLayer.Hair, this.customHairs[0, 0], 0, ClothesIntention.None, ColorScheme.Modern) {Icon = this.Icon}); - // 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(SitDownOnGrassAction)) { - // we set this action to be executable only on grass tiles, not on other ground - CanExecute = (info, automatic) => { - if (!info.Map.IsInBounds(info.ActionLocation.ToPoint())) - return ActionType.CanExecuteResult.Hidden; - var tile = info.Map.GetTile(info.ActionLocation.ToPoint()); - if (tile.Name.StartsWith("Grass")) - return ActionType.CanExecuteResult.Valid; - // hidden means the action won't be displayed in the ring menu + // 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.TryGetValue(ClothesLayer.Shirt, out var shirt) && shirt.Type == darkShirt) + s *= 2; + }; + } + }; + + // 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(SitDownOnGrassAction)) { + // we set this action to be executable only on grass tiles, not on other ground + CanExecute = (info, automatic) => { + if (!info.Map.IsInBounds(info.ActionLocation.ToPoint())) return ActionType.CanExecuteResult.Hidden; - }, - Ai = { - // we allow the action to be done even if the solved needs aren't low enough on a person - CanDoRandomly = true, - SolvedNeeds = new[] {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[1, 0] - }); - GrassSittingModifier = EmotionModifier.Register( - new EmotionModifier("ExampleMod.GrassSitting", this.uiTextures[1, 0], EmotionType.Happy)); - } - - 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("CustomTops"), r => this.customTops = new UniformTextureAtlas(r, 4, 8)); - texturePacker.Add(content.Load("CustomHairs"), r => this.customHairs = new UniformTextureAtlas(r, 4, 6)); - texturePacker.Add(content.Load("CustomBottomsShoes"), r => this.customBottoms = new UniformTextureAtlas(r, 8, 6)); - texturePacker.Add(content.Load("UiTextures"), r => this.uiTextures = new UniformTextureAtlas(r, 8, 8)); - } - - public override IEnumerable 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"; - } - + var tile = info.Map.GetTile(info.ActionLocation.ToPoint()); + if (tile.Name.StartsWith("Grass")) + return ActionType.CanExecuteResult.Valid; + // hidden means the action won't be displayed in the ring menu + return ActionType.CanExecuteResult.Hidden; + }, + Ai = { + // we allow the action to be done even if the solved needs aren't low enough on a person + CanDoRandomly = true, + SolvedNeeds = new[] {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[1, 0] + }); + GrassSittingModifier = EmotionModifier.Register( + new EmotionModifier("ExampleMod.GrassSitting", this.uiTextures[1, 0], EmotionType.Happy)); } + + 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("CustomTops"), r => this.customTops = new UniformTextureAtlas(r, 4, 8)); + texturePacker.Add(content.Load("CustomHairs"), r => this.customHairs = new UniformTextureAtlas(r, 4, 6)); + texturePacker.Add(content.Load("CustomBottomsShoes"), r => this.customBottoms = new UniformTextureAtlas(r, 8, 6)); + texturePacker.Add(content.Load("UiTextures"), r => this.uiTextures = new UniformTextureAtlas(r, 8, 8)); + } + + public override IEnumerable 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"; + } + } \ No newline at end of file diff --git a/SitDownOnGrassAction.cs b/SitDownOnGrassAction.cs index 90c1b1f..5bea3ff 100644 --- a/SitDownOnGrassAction.cs +++ b/SitDownOnGrassAction.cs @@ -7,49 +7,49 @@ using TinyLife.Emotions; using TinyLife.Objects; using Action = TinyLife.Actions.Action; -namespace ExampleMod { - // we use a multi action because we want to walk to the location, and then execute the main sitting part - // see CustomTable for information on how to store custom action-specific information to disk as well - public class SitDownOnGrassAction : MultiAction { +namespace ExampleMod; - public SitDownOnGrassAction(ActionType type, ActionInfo info) : base(type, info) { - } - - protected override IEnumerable CreateFirstActions() { - // we want to walk to the location clicked, so we use the current action info - yield return ActionType.GoHere.Construct(this.Info); - } - - protected override void AndThenInitialize() { - // this is called when the main action starts (after going to the location, in our case) - // but we don't need to do anything here for our action - } - - protected override void AndThenUpdate(GameTime time, TimeSpan passedInGame, float speedMultiplier) { - base.AndThenUpdate(time, passedInGame, speedMultiplier); - // this method gets called every update frame while the action is active - - // set our person to look like they're sitting on the ground - this.Person.CurrentPose = Person.Pose.SittingGround; - - // restore need and lower emotions - this.Person.RestoreNeed(NeedType.Energy, 0.5F, 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.CompleteInTime(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)); - } - } +// we use a multi action because we want to walk to the location, and then execute the main sitting part +// see CustomTable for information on how to store custom action-specific information to disk as well +public class SitDownOnGrassAction : MultiAction { + public SitDownOnGrassAction(ActionType type, ActionInfo info) : base(type, info) { } + + protected override IEnumerable CreateFirstActions() { + // we want to walk to the location clicked, so we use the current action info + yield return ActionType.GoHere.Construct(this.Info); + } + + protected override void AndThenInitialize() { + // this is called when the main action starts (after going to the location, in our case) + // but we don't need to do anything here for our action + } + + protected override void AndThenUpdate(GameTime time, TimeSpan passedInGame, float speedMultiplier) { + base.AndThenUpdate(time, passedInGame, speedMultiplier); + // this method gets called every update frame while the action is active + + // set our person to look like they're sitting on the ground + this.Person.CurrentPose = Person.Pose.SittingGround; + + // restore need and lower emotions + this.Person.RestoreNeed(NeedType.Energy, 0.5F, 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.CompleteInTime(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)); + } + } + } \ No newline at end of file