diff --git a/ChristmasLights.sln b/ChristmasLights.sln new file mode 100644 index 0000000..2ecc5d1 --- /dev/null +++ b/ChristmasLights.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.4 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChristmasLights", "ChristmasLights\ChristmasLights.csproj", "{9F7A9832-8637-418D-A1DA-C59D63C8607F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9F7A9832-8637-418D-A1DA-C59D63C8607F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F7A9832-8637-418D-A1DA-C59D63C8607F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F7A9832-8637-418D-A1DA-C59D63C8607F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F7A9832-8637-418D-A1DA-C59D63C8607F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ChristmasLights/ChristmasLights.csproj b/ChristmasLights/ChristmasLights.csproj new file mode 100644 index 0000000..6674c54 --- /dev/null +++ b/ChristmasLights/ChristmasLights.csproj @@ -0,0 +1,84 @@ + + + + + Debug + AnyCPU + {9F7A9832-8637-418D-A1DA-C59D63C8607F} + Library + BrightExistence.ChristmasLights + ChristmasLights + v3.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + C:\Program Files (x86)\Steam\steamapps\common\Colony Survival\gamedata\mods\Pipliz\APIProvider\APIProvider.dll + False + + + C:\Program Files (x86)\Steam\steamapps\common\Colony Survival\colonyserver_Data\Managed\Assembly-CSharp.dll + False + + + C:\Program Files (x86)\Steam\steamapps\common\Colony Survival\colonyserver_Data\Managed\UnityEngine.dll + False + + + C:\Program Files (x86)\Steam\steamapps\common\Colony Survival\colonyserver_Data\Managed\UnityEngine.UI.dll + False + + + + + + + + + + + + Always + + + Always + + + + + + + + Always + + + Always + + + Always + + + + \ No newline at end of file diff --git a/ChristmasLights/ItemHelper.cs b/ChristmasLights/ItemHelper.cs new file mode 100644 index 0000000..ff89c7d --- /dev/null +++ b/ChristmasLights/ItemHelper.cs @@ -0,0 +1,404 @@ +using System; +using System.Collections.Generic; +using Pipliz.APIProvider.Jobs; +using Pipliz.JSON; +using System.IO; + +namespace BrightExistence +{ + public static class ItemHelper + { + /// + /// Attempts to remove an item from the server's database. + /// + /// string: Item's Key. + /// True if item was removed. False if it was not for any reason. + public static bool tryRemoveItem (string itemName) + { + if (itemName == null || itemName.Length < 1) + { + Pipliz.Log.WriteError("{0}: tryRemoveItem has been called but was not given a valid item identifier.", Variables.NAMESPACE); + return false; + } + else + { + if (Variables.itemsMaster == null) + { + Pipliz.Log.WriteError("{0}: tryRemoveItem was called on {1} before Items master dictionary has been obtained. Cannot complete action.", Variables.NAMESPACE, itemName); + return false; + } + else + { + if (!Variables.itemsMaster.ContainsKey(itemName)) + { + Pipliz.Log.WriteError("{0}: tryRemoveItem was called on key {1} that was not found.", Variables.NAMESPACE, itemName); + return false; + } + else + { + Pipliz.Log.Write("{0}: Item key {1} found, attempting removal", Variables.NAMESPACE, itemName); + Variables.itemsMaster.Remove(itemName); + + if (!Variables.itemsMaster.ContainsKey(itemName)) + { + Pipliz.Log.Write("{0}: Item {1} successfully removed.", Variables.NAMESPACE, itemName); + return true; + } + else + { + Pipliz.Log.Write("{0}: Item {1} removal was not successful for an unknown reason.", Variables.NAMESPACE, itemName); + return false; + } + } + } + } + } + } + + public class SimpleItem + { + /// + /// Stores the mod's namespace. + /// + public string NAMESPACE { get; protected set; } + + /// + /// Name of Item, excluding prefix. Ex: myItem instead of myHandle.myMod.myItem + /// + public string Name { get; set; } + + /// + /// Path to icon .png file Ex: gamedata/textures/icons/vanillaIconName.png or getLocalIcon("myIconFile.png") + /// + public string Icon { get; set; } + + /// + /// Can the item be placed in the world; is it a block? + /// + public bool? isPlaceable { get; set; } + + /// + /// When a player attempts to remove the block, is it actually removed? + /// + public bool? isDestructible { get; set; } + + /// + /// Is it a solid block, or can players and NPCs walk through it? + /// + public bool? isSolid { get; set; } + + /// + /// The name of the texture that will be displayed on all sides of the block unless otherwise specified. + /// + public string sideAll { get; set; } + + /// + /// The name of the texture that will be displayed on the top (y+) side of the block only. + /// + public string sideTop { get; set; } + + /// + /// The name of the texture that will be displayed on the bottom (y-) side of the block only. + /// + public string sideBottom { get; set; } + + /// + /// The name of the texture which will be displayed on the front (z+) side of the block only. + /// + public string sideFront { get; set; } + + /// + /// The name of the texture which will be displayed on the back (z-) side of the block only. + /// + public string sideBack { get; set; } + + /// + /// The name of the texture which will be displayed on the left (x-) side of the block only. + /// + public string sideLeft { get; set; } + + /// + /// The name of the texture which will be displayed on the right (x+) side of the block only. + /// + public string sideRight { get; set; } + + /// + /// The location of a file that is the mesh for this item. If omitted, the item will be a perfect cube. + /// + public string mesh { get; set; } + + /// + /// The amount of time the user must hold down the left mouse button to remove a block of this item. + /// + public int? destructionTime = 500; + + /// + /// The name of an audio asset which will be played when a block of this item is placed. + /// + public string onPlaceAudio { get; set; } + + /// + /// The name of an audio asset which will be played when a block of this item is removed. + /// + public string onRemoveAudio { get; set; } + + /// + /// If true, the registerAsCrate method will register this item as a crate (a type of tracked block) when called during the proper callback. + /// + public bool isCrate = false; + + /// + /// A list of DropItem objects describing what types are added to inventory when a block of this type is removed, and by what chance. + /// + public List Drops = new List(); + + /// + /// Will things grow on it? + /// + public bool? isFertile = false; + + /// + /// Can it be mined by NPCs + /// + public bool? minerIsMineable = false; + + /// + /// How quickly do NPCs mine it, if they're allowed to? + /// + public int minerMiningTime = 2; + + /// + /// Replaces an item of this key in the server's item database. + /// + public string maskItem; + + /// + /// Used to make the block glow by using a SimpleItem.Light object. + /// + public SimpleItem.Light lightSource; + + /// + /// The ID, or name of this item as it will be stored in the server database. + /// + public string ID + { + get + { + if (maskItem == null) return NAMESPACE + "." + Name; + else return maskItem; + } + } + + protected ItemTypesServer.ItemTypeRaw itrThisItem; + protected ItemTypesServer.ItemTypeRaw thisItemRaw + { + get + { + if (itrThisItem == null) buildItemRaw(); + return itrThisItem; + } + set + { + itrThisItem = value; + } + } + + /// + /// Constructor + /// + /// Namespace of mod. Ex: DeveloperHandle.ModName Will be used as a prefix to generate item IDs. + /// Name of item excluding any prefixes. Ex: MyItem NOT DeveloperHandle.ModName.MyItem + public SimpleItem(string strName, bool dropsSelf = true, string strNAMESPACE = Variables.NAMESPACE) + { + NAMESPACE = strNAMESPACE == null ? "" : strNAMESPACE; + Name = (strName == null || strName.Length < 1) ? "NewItem" : strName; + if (dropsSelf) Drops.Add(new DropItem(this.ID)); + Pipliz.Log.Write("{0}: Initialized Item {1} (it is not yet registered.)", Variables.NAMESPACE, this.Name); + try + { + if (!Variables.Items.Contains(this)) Variables.Items.Add(this); + } + catch (Exception) + { + Pipliz.Log.Write("{0} : WARNING : Item {1} could not be automatically added to auto-load list. Make sure you explicityly added it.", Variables.NAMESPACE, this.Name); + } + } + + /// + /// Registers this item in the server's database of items.Should be called during the afterAddingBaseTypes callback. + /// + /// The server's item database (a Dictionary object). Will be passed to the afterAddingBaseTypes callback method. + public void registerItem(Dictionary items) + { + Pipliz.Log.Write("{0}: Preparing to register block {1} to ID {2}", Variables.NAMESPACE, this.Name, this.ID); + + // Remove masked item, if there is one. + if (maskItem != null) ItemHelper.tryRemoveItem(maskItem); + + Pipliz.Log.Write("{0}: Registering item {1} as {2} (this is a masking: {3})", Variables.NAMESPACE, this.Name, this.ID, Convert.ToString(this.maskItem != null)); + items.Add(this.ID, thisItemRaw); + + Pipliz.Log.Write("{0}: Block {1} has been registered to ID {2}", Variables.NAMESPACE, this.Name, this.ID); + } + + /// + /// Registers this block as a crate if the isCrate property is set to true. Should be called during the AfterItemTypesDefined callback. + /// + public void registerAsCrate() + { + if (this.isCrate) + { + Pipliz.Log.Write("{0}: Attempting to register {1} as a crate.", Variables.NAMESPACE, this.ID); + + try + { + ItemTypesServer.RegisterOnAdd(this.ID, StockpileBlockTracker.Add); + ItemTypesServer.RegisterOnRemove(this.ID, StockpileBlockTracker.Remove); + } + catch (Exception ex) + { + Pipliz.Log.Write("{0}: Crate registration error: {1}", Variables.NAMESPACE, ex.Message); + } + } + } + + /// + /// Associates a job class to this block. + /// + /// A class which describes the job being associated with the block, must impliment ITrackableBlock, + /// IBlockJobBase, INPCTypeDefiner, and have a default constructor. Should be called during the AfterDefiningNPCTypes callback. + public void registerJob() where T : ITrackableBlock, IBlockJobBase, INPCTypeDefiner, new() + { + Pipliz.Log.Write("{0}: Attempting to register a job to block {1}", Variables.NAMESPACE, this.ID); + try + { + BlockJobManagerTracker.Register(this.ID); + } + catch (Exception ex) + { + Pipliz.Log.Write("{0}: Registration error: {1}", Variables.NAMESPACE, ex.Message); + } + } + + /// + /// Builds an ItemTypeServer.ItemTypeRaw object based on this object's data and registers it as a block (named as this object's ID property.) + /// + protected void buildItemRaw() + { + JSONNode thisItemJSON = new JSONNode(); + if (Icon != null) thisItemJSON.SetAs("icon", Icon); + if (isPlaceable != null) thisItemJSON.SetAs("isPlaceable", isPlaceable); + if (isDestructible != null) thisItemJSON.SetAs("isDestructible", isDestructible); + if (isSolid != null) thisItemJSON.SetAs("isSolid", isSolid); + if (this.Drops.Count > 0) + { + JSONNode DropsNode = new JSONNode(NodeType.Array); + foreach (SimpleItem.DropItem thisDrop in Drops) + { + DropsNode.AddToArray(thisDrop.asJSONNode()); + } + } + if (sideAll != null) thisItemJSON.SetAs("sideall", sideAll); + if (destructionTime != null) thisItemJSON.SetAs("destructionTime", destructionTime); + if (isFertile != null) thisItemJSON.SetAs("isFertile", isFertile); + if (mesh != null) thisItemJSON.SetAs("mesh", mesh); + if (minerIsMineable != null || lightSource != null) + { + JSONNode customData = new JSONNode(); + if (minerIsMineable != null && minerIsMineable == true) + { + JSONNode MiningData = new JSONNode(); + customData.SetAs("minerIsMineable", true); + customData.SetAs("minerMiningTime", minerMiningTime); + } + if (lightSource != null) + { + customData.SetAs("torches", lightSource.asJSONNode()); + } + thisItemJSON.SetAs("customData", customData); + } + if (sideTop != null) thisItemJSON.SetAs("sidey+", sideTop); + if (sideBottom != null) thisItemJSON.SetAs("sidey-", sideBottom); + if (sideFront != null) thisItemJSON.SetAs("sidez+", sideFront); + if (sideBack != null) thisItemJSON.SetAs("sidez-", sideBack); + if (sideLeft != null) thisItemJSON.SetAs("sidex-", sideLeft); + if (sideRight != null) thisItemJSON.SetAs("sidex+", sideRight); + if (onPlaceAudio != null) thisItemJSON.SetAs("onPlaceAudio", onPlaceAudio); + if (onRemoveAudio != null) thisItemJSON.SetAs("onRemoveAudio", onRemoveAudio); + this.itrThisItem = new ItemTypesServer.ItemTypeRaw(this.ID, thisItemJSON); + /* + using (StreamWriter toJSON = new StreamWriter(this.Name + ".JSON")) + { + thisItemJSON.Serialize(toJSON,0); + } + */ + Pipliz.Log.Write("{0}: Created raw item type {1}, not yet registered.", Variables.NAMESPACE, this.Name); + } + + /// + /// Helper class used in building ItemTypeRaw JSONs + /// + public struct DropItem + { + string type; + int amount; + float chance; + + public DropItem(string strType) + { + type = strType; + amount = 1; + chance = 1f; + } + + public DropItem(string strType, int intAmount) + { + type = strType; + amount = intAmount; + chance = 1f; + } + + public DropItem(string strType, int intAmount, float fltChance) + { + type = strType; + amount = intAmount; + chance = fltChance; + } + + public JSONNode asJSONNode () + { + JSONNode returnMe = new JSONNode(); + returnMe.SetAs("type", type); + returnMe.SetAs("amount", amount); + returnMe.SetAs("chance", chance); + + return returnMe; + } + } + + public class Light + { + public float volume = 0.5f; + public float intensity = 10f; + public int range = 10; + public float red = 195f; + public float green = 135f; + public float blue = 46f; + + public JSONNode asJSONNode () + { + JSONNode torches = new JSONNode(); + JSONNode a = new JSONNode(); + a.SetAs("volume", volume); + a.SetAs("intensity", intensity); + a.SetAs("range", range); + a.SetAs("red", red); + a.SetAs("green", green); + a.SetAs("blue", blue); + torches.SetAs("a", a); + + return torches; + } + } + } +} diff --git a/ChristmasLights/RecipeHelper.cs b/ChristmasLights/RecipeHelper.cs new file mode 100644 index 0000000..bf32f23 --- /dev/null +++ b/ChristmasLights/RecipeHelper.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; + +namespace BrightExistence +{ + public static class RecipeHelper + { + /// + /// Attempts to remove an existing recipe from the server's database. + /// + /// Name of recipe. + /// True if recipe was removed, False if recipe was not found or removal was not successful. + public static bool tryRemoveRecipe (string recName) + { + if (RecipeStorage.TryGetRecipe(recName, out Recipe Rec)) + { + Pipliz.Log.Write("{0}: Recipe {1} found, attempting to remove.", Variables.NAMESPACE, Rec.Name); + RecipeStorage.Recipes.Remove(recName); + + if (!RecipeStorage.TryGetRecipe(recName, out Recipe Rec2)) + { + Pipliz.Log.Write("{0}: Recipe {1} successfully removed", Variables.NAMESPACE, Rec.Name); + return true; + } + else + { + Pipliz.Log.Write("{0}: Recipe {1} removal failed for unknown reason.", Variables.NAMESPACE, Rec.Name); + return false; + } + } + else + { + Pipliz.Log.Write("{0}: Recipe {1} not found.", Variables.NAMESPACE, Rec.Name); + return false; + } + } + } + + public class SimpleRecipe + { + /// + /// Name of Recipe, excluding prefixs. Ex: myRecipe instead of myHandle.myMod.myRecipe + /// + public string Name = "New Recipe"; + /// + /// An InventoryItem list containing the items the user recieves when this recipe is completed. May be ignored if the constructor + /// which takes a SimpleItem object is used. + /// + public List Results = new List(); + /// + /// An InventoryItem list containing the items necessary to complete this recipe. + /// + public List Requirements = new List(); + /// + /// The limitType, a.k.a. NPCTypeKey is essentially a group of recipes associated with a block and an NPC. Ex: pipliz.crafter + /// + public string limitType { get; set; } + /// + /// The default limit at which an NPC will stop crafting this recipe. + /// + public int defaultLimit = 1; + /// + /// The default priority of this recipe vs other recipes of the same limitType when crafted by an NPC. + /// + public int defaultPriority = 0; + /// + /// True if this recipe must be researched to be available, otherwise false. + /// + public bool isOptional = false; + /// + /// Set to true if you want addRecipeToLimitType() to create a copy of this recipe and add it to the list of recipes the players + /// themselves can craft. + /// + public bool userCraftable = false; + /// + /// Names what recipes, if any, this recipe is intended to replace. The named recipes will be deleted from the server's + /// database before this recipe is added. Use when replacing vanilla recipes. + /// + public List Replaces = new List(); + /// + /// A SimpleItem object which is the intended result of this recipe. + /// + protected SimpleItem FromItem; + + /// + /// The automatically generated name, including prefix, of this recipe. Ex: myHandle.myMod.myRecipe + /// + public string fullName + { + get + { + return limitType + "." + Name; + } + } + + /// + /// Constructor + /// + /// Name of recipe excluding any prefixes. Ex: myRecipe NOT myHandle.myMod.myRecipe + /// The limitType, a.k.a. NPCTypeKey is essentially a group of recipes associated with a block and an NPC. Ex: pipliz.crafter + public SimpleRecipe(string strName, string strLimitType) + { + this.Name = strName == null ? Variables.NAMESPACE + "NewRecipe" : strName; + this.limitType = strLimitType == null ? "" : strLimitType; + + Pipliz.Log.Write("{0}: Initialized Recipe {1} (it is not yet registered.)", Variables.NAMESPACE, this.Name); + try + { + if (!Variables.Recipes.Contains(this)) Variables.Recipes.Add(this); + } + catch (Exception) + { + Pipliz.Log.Write("{0} : WARNING : Recipe {1} could not be automatically added to auto-load list. Make sure you explicityly added it.", Variables.NAMESPACE, this.Name); + } + } + + /// + /// Constructor + /// + /// A SimpleItem object holding a type which is the intended result of this recipe. + /// The limitType, a.k.a. NPCTypeKey is essentially a group of recipes associated with a block and an NPC. Ex: pipliz.crafter + public SimpleRecipe(SimpleItem Item, string strLimitType) + { + if (Item == null || Item.Name == null || Item.Name.Length < 1) + { + throw new ArgumentException(Variables.NAMESPACE + ": Simple recipe cannot initialize when given a null Item or an Item with a Name of less than one character."); + } + else + { + FromItem = Item; + this.limitType = strLimitType == null ? "" : strLimitType; + this.Name = Item.Name; + + Pipliz.Log.Write("{0}: Initialized Recipe {1} (it is not yet registered.)", Variables.NAMESPACE, Name); + try + { + if (!Variables.Recipes.Contains(this)) Variables.Recipes.Add(this); + } + catch (Exception) + { + Pipliz.Log.Write("{0} : WARNING : Recipe {1} could not be automatically added to auto-load list. Make sure you explicityly added it.", Variables.NAMESPACE, this.Name); + } + } + } + + /// + /// Does all the work of adding this recipe to the server's database. Should be called in the AfterItemTypesDefined callback. + /// + public void addRecipeToLimitType() + { + try + { + // First remove any recipes we are replacing. + foreach (string deleteMe in Replaces) + { + Pipliz.Log.Write("{0}: Recipe {1} is marked as replacing {2}, attempting to comply.", Variables.NAMESPACE, this.Name, deleteMe); + RecipeHelper.tryRemoveRecipe(deleteMe); + } + + // If we're building the recipe from an item, assume a default result: + if (FromItem != null) + { + this.Results.Add(new InventoryItem(FromItem.ID)); + } + + // Build new Recipe object. + Recipe thisRecipe = new Recipe(this.fullName, this.Requirements, this.Results, this.defaultLimit, this.isOptional, this.defaultPriority); + + // Commence registering it. + Pipliz.Log.Write("SimpleItem: Attempting to register recipe {0} to block {1}", thisRecipe.Name, limitType); + if (isOptional) + { + Pipliz.Log.Write("{0}: Attempting to register optional limit type recipe {1}", Variables.NAMESPACE, thisRecipe.Name); + RecipeStorage.AddOptionalLimitTypeRecipe(limitType, thisRecipe); + } + else + { + Pipliz.Log.Write("{0}: Attempting to register default limit type recipe {1}", Variables.NAMESPACE, thisRecipe.Name); + RecipeStorage.AddDefaultLimitTypeRecipe(limitType, thisRecipe); + } + + if (userCraftable) + { + Recipe playerRecipe = new Recipe("player." + this.Name, this.Requirements, this.Results, this.defaultLimit, this.isOptional); + Pipliz.Log.Write("{0}: Attempting to register default player type recipe {1}", Variables.NAMESPACE, playerRecipe.Name); + RecipePlayer.AddDefaultRecipe(playerRecipe); + } + } + catch (Exception ex) + { + Pipliz.Log.WriteError("{0}: Error adding recipe to limit type: {1}", Variables.NAMESPACE, ex.Message); + } + } + } +} diff --git a/ChristmasLights/TextureHelper.cs b/ChristmasLights/TextureHelper.cs new file mode 100644 index 0000000..5941138 --- /dev/null +++ b/ChristmasLights/TextureHelper.cs @@ -0,0 +1,97 @@ + + +using System; + +namespace BrightExistence +{ + public static class TextureHelper + { + } + + /// + /// Front-end for built-in ItemTypesServer.TextureMapping class to enable auto-registration. + /// + public class SimpleTexture + { + /// + /// Name of texture, excluding any prefixes. Ex: myTexture NOT myHandle.myMod.myTexture + /// + public string Name { get; protected set; } + + /// + /// Prefix used to generate ID. Ex: myHandle.myMod + /// + public string NAMESPACE { get; protected set; } + + public string AlbedoPath; + + public string NormalPath; + + public string EmissivePath; + + public string HeightPath; + + /// + /// The string by which this texture will be referenced. + /// + public string ID + { + get + { + return NAMESPACE + "." + Name; + } + } + + /// + /// Constructor for SimpleTexture. + /// + /// Name of texture, excluding any prefixes. Ex: myTexture NOT myHandle.myMod.myTexture + /// Prefix used to generate ID. Ex: myHandle.myMod + public SimpleTexture(string strName, string strNAMESPACE) + { + Name = (strName == null || strName.Length < 1) ? "NewTexture" : strName; + NAMESPACE = strNAMESPACE == null ? "" : strNAMESPACE; + Pipliz.Log.Write("{0}: Initializing texture {1}, it is not yet registered.", Variables.NAMESPACE, this.Name); + try + { + if (!Variables.Textures.Contains(this)) Variables.Textures.Add(this); + } + catch (Exception) + { + Pipliz.Log.Write("{0} : WARNING : Texture {1} could not be automatically added to auto-load list. Make sure you explicityly added it.", Variables.NAMESPACE, this.Name); + } + } + + /// + /// Returns this item as a ItemTypeServer.TextureMapping struct. (Note this will strip name, ID, and namespace properties.) + /// + /// ItemTypeServer.TextureMapping struct + protected ItemTypesServer.TextureMapping asTextureMapping () + { + ItemTypesServer.TextureMapping thisMapping = new ItemTypesServer.TextureMapping(new Pipliz.JSON.JSONNode()); + if (this.AlbedoPath != null) thisMapping.AlbedoPath = this.AlbedoPath; + if (this.NormalPath != null) thisMapping.NormalPath = this.NormalPath; + if (this.EmissivePath != null) thisMapping.EmissivePath = this.EmissivePath; + if (this.HeightPath != null) thisMapping.HeightPath = this.HeightPath; + return thisMapping; + } + + /// + /// Registers this texture in the server database. Should be called during the afterSelectedWorld callback method. + /// + public void registerTexture () + { + Pipliz.Log.Write("Registering texture as "+ this.ID + " using file: " + this.AlbedoPath); + if (System.IO.File.Exists(this.AlbedoPath)) + { + Pipliz.Log.Write("{0}: Looks good, file exists.", Variables.NAMESPACE); + } + else + { + Pipliz.Log.WriteError("{0}: ERROR! Registering texture to a file which does not exist!", Variables.NAMESPACE); + } + ItemTypesServer.SetTextureMapping(this.ID, this.asTextureMapping()); + Pipliz.Log.Write("Texture registered: "+ this.Name); + } + } +} diff --git a/ChristmasLights/Variables.cs b/ChristmasLights/Variables.cs new file mode 100644 index 0000000..ca55bc0 --- /dev/null +++ b/ChristmasLights/Variables.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BrightExistence +{ + static class Variables + { + public static string ModGamedataDirectory; + public static string JobsPath; + public static string IconPath; + public static string TexturePath; + public static string MeshPath; + public static string ResearchablesPath; + public const string NAMESPACE = "BrightExistence.ChristmasLights"; + public static Dictionary itemsMaster; + + // AUTO-REGISTERED TEXTURES + public static List Textures = new List(); + + // AUTO-REGISTERED ITEMS + public static List Items = new List(); + + // AUTO-REGISTERED RECIPES + public static List Recipes = new List(); + } +} diff --git a/ChristmasLights/icons/leaveschristmas.png b/ChristmasLights/icons/leaveschristmas.png new file mode 100644 index 0000000..19ed5f9 Binary files /dev/null and b/ChristmasLights/icons/leaveschristmas.png differ diff --git a/ChristmasLights/localization/en-US/translation.json b/ChristmasLights/localization/en-US/translation.json new file mode 100644 index 0000000..25e3db3 --- /dev/null +++ b/ChristmasLights/localization/en-US/translation.json @@ -0,0 +1,10 @@ +{ + "types": { + "BrightExistence.ChristmasLights.leavesLit": "Christmas Leaves" + + }, + "typeuses": { + "BrightExistence.ChristmasLights.leavesLit": "Make your own Christmas Tree!" + + } +} \ No newline at end of file diff --git a/ChristmasLights/main.cs b/ChristmasLights/main.cs new file mode 100644 index 0000000..b3d53e1 --- /dev/null +++ b/ChristmasLights/main.cs @@ -0,0 +1,282 @@ +using Pipliz.JSON; +using System.Collections.Generic; +using System.IO; +using System; +using System.Text; +using BrightExistence; + +namespace BrightExistence.ChristmasLights +{ + [ModLoader.ModManager] + public static class Main + { + // GENERAL CLASS MEMBERS + + + // TEXTURES + static SimpleTexture ChristmasLeaves = new SimpleTexture("christmasleaves", Variables.NAMESPACE); + + // ITEMS + static SimpleItem LeavesLit = new SimpleItem("leavesLit"); + + // RECIPES + static SimpleRecipe ChristmasLightsRecipe = new SimpleRecipe(LeavesLit, "pipliz.crafter"); + + /// + /// OnAssemblyLoaded callback entrypoint. Used for mod configuration / setup. + /// + /// The starting point of mod file structure. + [ModLoader.ModCallback(ModLoader.EModCallbackType.OnAssemblyLoaded, Variables.NAMESPACE + ".OnAssemblyLoaded")] + public static void OnAssemblyLoaded (string path) + { + // Announce ourselves. + Pipliz.Log.Write("Mod {0} loading.", Variables.NAMESPACE); + + // Get a properly formatted version of our mod directory and subdirectories. + Variables.ModGamedataDirectory = Path.GetDirectoryName(path).Replace("\\", "/"); + Variables.JobsPath = Path.Combine(Variables.ModGamedataDirectory, "jobs").Replace("\\", "/"); + Variables.IconPath = Path.Combine(Variables.ModGamedataDirectory, "icons").Replace("\\", "/"); + Variables.TexturePath = Path.Combine(Variables.ModGamedataDirectory, "Textures").Replace("\\", "/"); + Variables.MeshPath = Path.Combine(Variables.ModGamedataDirectory, "meshes").Replace("\\", "/"); + Variables.ResearchablesPath = Path.Combine(Variables.ModGamedataDirectory, "researchables").Replace("\\", "/"); + } + + /// + /// AfterSelectedWorld callback entry point. Used for adding textures. + /// + [ModLoader.ModCallback(ModLoader.EModCallbackType.AfterSelectedWorld, Variables.NAMESPACE + ".afterSelectedWorld"), ModLoader.ModCallbackProvidesFor("pipliz.server.registertexturemappingtextures")] + public static void afterSelectedWorld() + { + // ---------------POPULATE TEXTURES HERE--------------- + ChristmasLeaves.AlbedoPath = Variables.TexturePath + "/albedo/leaveschristmas.png"; + ChristmasLeaves.EmissivePath = Variables.TexturePath + "/emissive/leaveschristmas.png"; + + // ---------------AUTOMATED TEXTURE REGISTRATION--------------- + foreach (SimpleTexture thisTexture in Variables.Textures) thisTexture.registerTexture(); + Pipliz.Log.Write("{0}: Texture loading complete.", Variables.NAMESPACE); + } + + /// + /// The afterAddingBaseTypes entrypoint. Used for adding blocks. + /// + [ModLoader.ModCallback(ModLoader.EModCallbackType.AfterAddingBaseTypes, Variables.NAMESPACE + ".afterAddingBaseTypes")] + public static void afterAddingBaseTypes(Dictionary items) + { + Variables.itemsMaster = items; + // ---------------POPULATE ITEM OBJECTS HERE--------------- + + // Christmas leaves + LeavesLit.Icon = getLocalIcon("leaveschristmas"); + LeavesLit.isSolid = true; + LeavesLit.isDestructible = true; + LeavesLit.isPlaceable = true; + LeavesLit.sideAll = ChristmasLeaves.ID; + LeavesLit.lightSource = new SimpleItem.Light(); + LeavesLit.lightSource.intensity = 1f; + LeavesLit.lightSource.range = 50; + + // ---------------(AUTOMATED BLOCK REGISTRATION)--------------- + foreach (SimpleItem Item in Variables.Items) Item.registerItem(items); + Pipliz.Log.Write("{0}: Block and Item loading complete.", Variables.NAMESPACE); + } + + /// + /// The afterItemType callback entrypoint. Used for registering jobs and recipes. + /// + [ModLoader.ModCallback(ModLoader.EModCallbackType.AfterItemTypesDefined, Variables.NAMESPACE + ".AfterItemTypesDefined")] + public static void AfterItemTypesDefined() + { + //---------------POPULATE RECIPES HERE--------------- + + // Christmas Leaves + ChristmasLightsRecipe.Requirements.Add(new InventoryItem("leavestemperate", 1)); + ChristmasLightsRecipe.userCraftable = true; + ChristmasLightsRecipe.defaultLimit = 15; + ChristmasLightsRecipe.defaultPriority = -100; + + + //---------------AUTOMATED RECIPE REGISTRATION--------------- + foreach (SimpleRecipe Rec in Variables.Recipes) Rec.addRecipeToLimitType(); + Pipliz.Log.Write("{0}: Recipe and Job loading complete.", Variables.NAMESPACE); + + //---------------AUTOMATED INVENTORY BLOCK REGISTRATION--------------- + foreach (SimpleItem Item in Variables.Items) Item.registerAsCrate(); + Pipliz.Log.Write("{0}: Crate registration complete.", Variables.NAMESPACE); + + //---------------STARTING INVENTORY ADJUSTMENTS--------------- + Inventory.AddToInitialPile(new InventoryItem(ChristmasLeaves.ID)); + } + + /// + /// AfterDefiningNPCTypes callback. Used for registering jobs. + /// + [ModLoader.ModCallback(ModLoader.EModCallbackType.AfterItemTypesDefined, "BrightExistence.BrightColony.AfterDefiningNPCTypes")] + [ModLoader.ModCallbackProvidesFor("pipliz.apiprovider.jobs.resolvetypes")] + public static void AfterDefiningNPCTypes() + { + // ---------------REGISTER JOBS HERE--------------- + + //Bowyer.registerJob(); + + Pipliz.Log.Write("{0}: Job loading complete.", Variables.NAMESPACE); + } + + /// + /// OnLoadingPlayer callback, called each time a player is loaded. + /// + /// Player's data as JSON. + /// Player object. + [ModLoader.ModCallback(ModLoader.EModCallbackType.OnPlayerConnectedLate, Variables.NAMESPACE + ".OnPlayerConnectedLate")] + public static void OnPlayerConnectedLate(Players.Player p) + { + + } + + /// + /// AfterWorldLoad callback entry point. Used for localization routines. + /// + [ModLoader.ModCallback(ModLoader.EModCallbackType.AfterWorldLoad, Variables.NAMESPACE + ".AfterWorldLoad")] + [ModLoader.ModCallbackDependsOn("pipliz.server.localization.waitforloading")] + [ModLoader.ModCallbackProvidesFor("pipliz.server.localization.convert")] + public static void AfterWorldLoad () + { + try + { + string[] array = new string[] + { + "translation.json" + }; + for (int i = 0; i < array.Length; i++) + { + string text = array[i]; + string[] files = Directory.GetFiles(Path.Combine(Variables.ModGamedataDirectory,"localization"), text, SearchOption.AllDirectories); + string[] array2 = files; + for (int j = 0; j < array2.Length; j++) + { + string text2 = array2[j]; + try + { + JSONNode jsonFromMod; + if (JSON.Deserialize(text2, out jsonFromMod, false)) + { + string name = Directory.GetParent(text2).Name; + + if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(text)) + { + Pipliz.Log.Write("{0}: Found mod localization file for '{1}' localization", Variables.NAMESPACE, name); + Localize(name, text, jsonFromMod); + } + } + } + catch (Exception ex) + { + Pipliz.Log.Write("{0}: Exception reading localization from {1}; {2}", Variables.NAMESPACE, text2, ex.Message); + } + } + } + } + catch (DirectoryNotFoundException) + { + Pipliz.Log.Write("{0}: Localization directory not found at {1}", Variables.NAMESPACE, Path.Combine(Variables.ModGamedataDirectory,"localization")); + } + } + + + public static void Localize(string locName, string locFilename, JSONNode jsonFromMod) + { + try + { + if (Server.Localization.Localization.LoadedTranslation == null) + { + Pipliz.Log.Write("{0} :Unable to localize. Server.Localization.Localization.LoadedTranslation is null.", Variables.NAMESPACE); + } + else + { + if (Server.Localization.Localization.LoadedTranslation.TryGetValue(locName, out JSONNode jsn)) + { + if (jsn != null) + { + foreach (KeyValuePair modNode in jsonFromMod.LoopObject()) + { + Pipliz.Log.Write("{0} : Adding localization for '{1}' from '{2}'.", Variables.NAMESPACE, modNode.Key, Path.Combine(locName, locFilename)); + AddRecursive(jsn, modNode); + } + } + else + Pipliz.Log.Write("{0}: Unable to localize. Localization '{01 not found and is null.", Variables.NAMESPACE, locName); + } + else + Pipliz.Log.Write("{0}: Localization '{1}' not supported", Variables.NAMESPACE, locName); + } + + Pipliz.Log.Write("{0}: Patched mod localization file '{1}/{2}'", Variables.NAMESPACE, locName, locFilename); + + } + catch (Exception ex) + { + Pipliz.Log.WriteError(ex.ToString(), "{0}: Exception while localizing {1}", Variables.NAMESPACE, Path.Combine(locName, locFilename)); + } + } + + private static void AddRecursive(JSONNode gameJson, KeyValuePair modNode) + { + int childCount = 0; + + try + { + childCount = modNode.Value.ChildCount; + } + catch { } + + if (childCount != 0) + { + if (gameJson.HasChild(modNode.Key)) + { + foreach (var child in modNode.Value.LoopObject()) + AddRecursive(gameJson[modNode.Key], child); + } + else + { + gameJson[modNode.Key] = modNode.Value; + } + } + else + { + gameJson[modNode.Key] = modNode.Value; + } + } + + /// + /// Converts the name of an item to its in-game ID by prefixing NAMESPACE. + /// + /// Name of item. + /// Game ID of item. (NAMESPACE + Name) + public static string getLocalID (string itemName) + { + return Variables.NAMESPACE + "." + itemName; + } + + /// + /// Converts the name of an icon to a full path. + /// + /// Name of the icon. (NOT filename: no extension.) + /// Full path of icon file with extension. + public static string getLocalIcon (string iconName) + { + return Path.Combine(Variables.IconPath, iconName + ".png"); + } + } + + public static class MultiPath + { + public static string Combine(params string[] pathParts) + { + StringBuilder result = new StringBuilder(); + foreach (string part in pathParts) + { + result.Append(part.TrimEnd('/', '\\')).Append(Path.DirectorySeparatorChar); + } + return result.ToString().TrimEnd(Path.DirectorySeparatorChar); + } + } +} diff --git a/ChristmasLights/modInfo.json b/ChristmasLights/modInfo.json new file mode 100644 index 0000000..ebb8e2a --- /dev/null +++ b/ChristmasLights/modInfo.json @@ -0,0 +1,16 @@ +{ + "assemblies": [ + { + "name": "ChristmasLights", + "path": "ChristmasLights.dll", + "enabled": true, + "compatibleversions": [ + { + "Major": 0, + "Minor": 4, + "Build": 4 + } + ] + } + ] +} \ No newline at end of file diff --git a/ChristmasLights/textures/albedo/leaveschristmas.png b/ChristmasLights/textures/albedo/leaveschristmas.png new file mode 100644 index 0000000..054f12c Binary files /dev/null and b/ChristmasLights/textures/albedo/leaveschristmas.png differ diff --git a/ChristmasLights/textures/emissive/leaveschristmas.png b/ChristmasLights/textures/emissive/leaveschristmas.png new file mode 100644 index 0000000..1fc02da Binary files /dev/null and b/ChristmasLights/textures/emissive/leaveschristmas.png differ