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