protected virtual void OnTick(float dt) { if (Api.Side == EnumAppSide.Client) { // We don't have to do this client side. The item stack renderer already updates those states for us return; } room = roomReg.GetRoomForPosition(Pos); if (room.AnyChunkUnloaded != 0) { return; } foreach (ItemSlot slot in Inventory) { if (slot.Itemstack == null) { continue; } AssetLocation codeBefore = slot.Itemstack.Collectible.Code; slot.Itemstack.Collectible.UpdateAndGetTransitionStates(Api.World, slot); if (slot.Itemstack?.Collectible.Code != codeBefore) { MarkDirty(true); } } }
public float applyGreenhouseTempBonus(float temp) { if (Api.World.BlockAccessor.GetRainMapHeightAt(be.Pos) > be.Pos.Y) // Fast pre-check { Room room = roomreg?.GetRoomForPosition(be.Pos); int roomness = (room != null && room.SkylightCount > room.NonSkylightCount && room.ExitCount == 0) ? 1 : 0; if (roomness > 0) { temp += 5; } } return(temp); }
public override void Initialize(ICoreAPI api) { base.Initialize(api); Inventory.LateInitialize(InventoryClassName + "-" + Pos.X + "/" + Pos.Y + "/" + Pos.Z, api); Inventory.Pos = Pos; Inventory.ResolveBlocksOrItems(); Inventory.OnAcquireTransitionSpeed = Inventory_OnAcquireTransitionSpeed; if (api.Side == EnumAppSide.Client) { Inventory.OnInventoryOpened += Inventory_OnInventoryOpenedClient; } RegisterGameTickListener(OnTick, 10000); roomReg = Api.ModLoader.GetModSystem <RoomRegistry>(); room = roomReg.GetRoomForPosition(Pos); }
private void OnScanForEmptySkep(float dt) { Room room = roomreg?.GetRoomForPosition(Pos); roomness = (room != null && room.SkylightCount > room.NonSkylightCount && room.ExitCount == 0) ? 1 : 0; if (actvitiyLevel < 1) { return; } if (Api.Side == EnumAppSide.Client) { return; } if (Api.World.Calendar.TotalHours < cooldownUntilTotalHours) { return; } if (scanIteration == 0) { scanQuantityNearbyFlowers = 0; scanQuantityNearbyHives = 0; scanEmptySkeps.Clear(); } // Let's count/collect 3 things in a 20x20x20 cube // 1. All positions of empty skeps // 2. Amount of living beehives (skeps or wild) // 3. Amount of flowers // Default Spread speed: Once every 4 in game days * factor // Don't spread at all if 3 * livinghives + 3 > flowers // factor = Clamped(livinghives / Math.Sqrt(flowers - 3 * livinghives - 3), 1, 1000) // After spreading: 4 extra days cooldown int minX = -8 + 8 * (scanIteration / 2); int minZ = -8 + 8 * (scanIteration % 2); int size = 8; Block emptySkepN = Api.World.GetBlock(new AssetLocation("skep-empty-north")); Block emptySkepE = Api.World.GetBlock(new AssetLocation("skep-empty-east")); Block emptySkepS = Api.World.GetBlock(new AssetLocation("skep-empty-south")); Block emptySkepW = Api.World.GetBlock(new AssetLocation("skep-empty-west")); Block fullSkepN = Api.World.GetBlock(new AssetLocation("skep-populated-north")); Block fullSkepE = Api.World.GetBlock(new AssetLocation("skep-populated-east")); Block fullSkepS = Api.World.GetBlock(new AssetLocation("skep-populated-south")); Block fullSkepW = Api.World.GetBlock(new AssetLocation("skep-populated-west")); Block wildhive1 = Api.World.GetBlock(new AssetLocation("wildbeehive-medium")); Block wildhive2 = Api.World.GetBlock(new AssetLocation("wildbeehive-large")); Api.World.BlockAccessor.WalkBlocks(Pos.AddCopy(minX, -5, minZ), Pos.AddCopy(minX + size - 1, 5, minZ + size - 1), (block, pos) => { if (block.Id == 0) { return; } if (block.Attributes?.IsTrue("beeFeed") == true) { scanQuantityNearbyFlowers++; } if (block == emptySkepN || block == emptySkepE || block == emptySkepS || block == emptySkepW) { scanEmptySkeps.Add(pos.Copy()); } if (block == fullSkepN || block == fullSkepE || block == fullSkepS || block == fullSkepW || block == wildhive1 || block == wildhive2) { scanQuantityNearbyHives++; } }); scanIteration++; if (scanIteration == 4) { scanIteration = 0; OnScanComplete(); } }
private void CheckGrow(float dt) { if (Block.Attributes == null) { #if DEBUG Api.World.Logger.Notification("Ghost berry bush block entity at {0}. Block.Attributes is null, will remove game tick listener", Pos); foreach (long handlerId in TickHandlers) { Api.Event.UnregisterGameTickListener(handlerId); } #endif return; } // In case this block was imported from another older world. In that case lastCheckAtTotalDays would be a future date. lastCheckAtTotalDays = Math.Min(lastCheckAtTotalDays, Api.World.Calendar.TotalDays); // We don't need to check more than one year because it just begins to loop then double daysToCheck = GameMath.Mod(Api.World.Calendar.TotalDays - lastCheckAtTotalDays, Api.World.Calendar.DaysPerYear); bool changed = false; while (daysToCheck > 1f / Api.World.Calendar.HoursPerDay) { if (!changed) { if (Api.World.BlockAccessor.GetRainMapHeightAt(Pos) > Pos.Y) // Fast pre-check { Room room = roomreg?.GetRoomForPosition(Pos); roomness = (room != null && room.SkylightCount > room.NonSkylightCount && room.ExitCount == 0) ? 1 : 0; } else { roomness = 0; } } changed = true; daysToCheck -= 1f / Api.World.Calendar.HoursPerDay; lastCheckAtTotalDays += 1f / Api.World.Calendar.HoursPerDay; transitionHoursLeft -= 1f; ClimateCondition conds = Api.World.BlockAccessor.GetClimateAt(Pos, EnumGetClimateMode.ForSuppliedDateValues, lastCheckAtTotalDays); if (conds == null) { return; } if (roomness > 0) { conds.Temperature += 5; } bool reset = conds.Temperature < Block.Attributes["resetBelowTemperature"].AsFloat(-999); bool stop = conds.Temperature < Block.Attributes["stopBelowTemperature"].AsFloat(-999); bool revert = conds.Temperature < Block.Attributes["revertBlockBelowTemperature"].AsFloat(-999); if (stop || reset) { transitionHoursLeft += 1f; if (reset) { transitionHoursLeft = GetHoursForNextStage(); if (Block.Variant["state"] != "empty" && revert) { Block nextBlock = Api.World.GetBlock(Block.CodeWithVariant("state", "empty")); Api.World.BlockAccessor.ExchangeBlock(nextBlock.BlockId, Pos); } } continue; } if (transitionHoursLeft <= 0) { if (!DoGrow()) { return; } transitionHoursLeft = GetHoursForNextStage(); } } if (changed) { MarkDirty(false); } }
private void Update(float dt) { double hoursNextStage = GetHoursForNextStage(); bool nearbyWaterTested = false; double nowTotalHours = Api.World.Calendar.TotalHours; double hourIntervall = 3 + rand.NextDouble(); Block cropBlock = GetCrop(); bool hasCrop = cropBlock != null; bool skyExposed = Api.World.BlockAccessor.GetRainMapHeightAt(Pos.X, Pos.Z) <= (hasCrop ? Pos.Y + 1 : Pos.Y); if ((nowTotalHours - totalHoursLastUpdate) < hourIntervall) { if (updateMoistureLevel(Api.World.Calendar.TotalDays, lastWaterDistance, skyExposed)) { UpdateFarmlandBlock(); } return; } // Slow down growth on bad light levels int lightpenalty = 0; if (!allowundergroundfarming) { lightpenalty = Math.Max(0, Api.World.SeaLevel - Pos.Y); } int sunlight = Api.World.BlockAccessor.GetLightLevel(upPos, EnumLightLevelType.MaxLight); double lightGrowthSpeedFactor = GameMath.Clamp(1 - (delayGrowthBelowSunLight - sunlight - lightpenalty) * lossPerLevel, 0, 1); Block upblock = Api.World.BlockAccessor.GetBlock(upPos); Block deadCropBlock = Api.World.GetBlock(new AssetLocation("deadcrop")); double lightHoursPenalty = hoursNextStage / lightGrowthSpeedFactor - hoursNextStage; double totalHoursNextGrowthState = totalHoursForNextStage + lightHoursPenalty; EnumSoilNutrient?currentlyConsumedNutrient = null; if (upblock.CropProps != null) { currentlyConsumedNutrient = upblock.CropProps.RequiredNutrient; } // Let's increase fertility every 3-4 game hours bool growTallGrass = false; float[] npkRegain = new float[3]; float waterDistance = 99; // Don't update more than a year totalHoursLastUpdate = Math.Max(totalHoursLastUpdate, nowTotalHours - Api.World.Calendar.DaysPerYear * Api.World.Calendar.HoursPerDay); bool hasRipeCrop = HasRipeCrop(); if (!skyExposed) // Fast pre-check { Room room = roomreg?.GetRoomForPosition(upPos); roomness = (room != null && room.SkylightCount > room.NonSkylightCount && room.ExitCount == 0) ? 1 : 0; } else { roomness = 0; } ClimateCondition baseClimate = Api.World.BlockAccessor.GetClimateAt(Pos, EnumGetClimateMode.WorldGenValues); if (baseClimate == null) { return; } float baseTemperature = baseClimate.Temperature; // Fast forward in 3-4 hour intervalls while ((nowTotalHours - totalHoursLastUpdate) > hourIntervall) { if (!nearbyWaterTested) { EnumWaterSearchResult res; waterDistance = GetNearbyWaterDistance(out res); if (res == EnumWaterSearchResult.Deferred) { return; // Wait with updating until neighbouring chunks are loaded } if (res == EnumWaterSearchResult.NotFound) { waterDistance = 99; } nearbyWaterTested = true; lastWaterDistance = waterDistance; } updateMoistureLevel(totalHoursLastUpdate / Api.World.Calendar.HoursPerDay, waterDistance, skyExposed, baseClimate); totalHoursLastUpdate += hourIntervall; hourIntervall = 3 + rand.NextDouble(); baseClimate.Temperature = baseTemperature; ClimateCondition conds = Api.World.BlockAccessor.GetClimateAt(Pos, baseClimate, EnumGetClimateMode.ForSuppliedDate_TemperatureOnly, totalHoursLastUpdate / Api.World.Calendar.HoursPerDay); if (roomness > 0) { conds.Temperature += 5; } if (!hasCrop) { ripeCropColdDamaged = false; unripeCropColdDamaged = false; unripeHeatDamaged = false; for (int i = 0; i < damageAccum.Length; i++) { damageAccum[i] = 0; } } else { if (cropBlock?.CropProps != null && conds.Temperature < cropBlock.CropProps.ColdDamageBelow) { if (hasRipeCrop) { ripeCropColdDamaged = true; } else { unripeCropColdDamaged = true; damageAccum[(int)EnumCropStressType.TooCold] += (float)hourIntervall; } } else { damageAccum[(int)EnumCropStressType.TooCold] = Math.Max(0, damageAccum[(int)EnumCropStressType.TooCold] - (float)hourIntervall / 10); } if (cropBlock?.CropProps != null && conds.Temperature > cropBlock.CropProps.HeatDamageAbove && hasCrop) { unripeHeatDamaged = true; damageAccum[(int)EnumCropStressType.TooHot] += (float)hourIntervall; } else { damageAccum[(int)EnumCropStressType.TooHot] = Math.Max(0, damageAccum[(int)EnumCropStressType.TooHot] - (float)hourIntervall / 10); } for (int i = 0; i < damageAccum.Length; i++) { float dmg = damageAccum[i]; if (!allowcropDeath) { dmg = damageAccum[i] = 0; } if (dmg > 48) { Api.World.BlockAccessor.SetBlock(deadCropBlock.Id, upPos); var be = Api.World.BlockAccessor.GetBlockEntity(upPos) as BlockEntityDeadCrop; be.Inventory[0].Itemstack = new ItemStack(cropBlock); be.deathReason = (EnumCropStressType)i; hasCrop = false; break; } } } // Stop growth and fertility recovery below zero degrees // 10% growth speed at 1°C // 20% growth speed at 2°C and so on float growthChance = GameMath.Clamp(conds.Temperature / 10f, 0, 10); if (rand.NextDouble() > growthChance) { continue; } growTallGrass |= rand.NextDouble() < 0.006; bool ripe = HasRipeCrop(); // Rule 1: Fertility increase up to original levels by 1 every 3-4 ingame hours // Rule 2: Fertility does not increase with a ripe crop on it npkRegain[0] = ripe ? 0 : 0.33f; npkRegain[1] = ripe ? 0 : 0.33f; npkRegain[2] = ripe ? 0 : 0.33f; // Rule 3: Fertility increase up 3 times slower for the currently growing crop if (currentlyConsumedNutrient != null) { npkRegain[(int)currentlyConsumedNutrient] /= 3; } for (int i = 0; i < 3; i++) { nutrients[i] += Math.Max(0, npkRegain[i] + Math.Min(0, originalFertility[i] - nutrients[i] - npkRegain[i])); // Rule 4: Slow release fertilizer can fertilize up to 100 fertility if (slowReleaseNutrients[i] > 0) { float release = Math.Min(0.33f, slowReleaseNutrients[i]); nutrients[i] = Math.Min(100, nutrients[i] + release); slowReleaseNutrients[i] = Math.Max(0, slowReleaseNutrients[i] - release); } else { // Rule 5: Once the slow release fertilizer is consumed, the soil will slowly return to its original fertility if (nutrients[i] > originalFertility[i]) { nutrients[i] = Math.Max(originalFertility[i], nutrients[i] - 0.05f); } } } if (moistureLevel < 0.1) { // Too dry to grow. Todo: Make it dependent on crop continue; } if (totalHoursNextGrowthState <= totalHoursLastUpdate) { TryGrowCrop(totalHoursForNextStage); totalHoursForNextStage += hoursNextStage; totalHoursNextGrowthState = totalHoursForNextStage + lightHoursPenalty; hoursNextStage = GetHoursForNextStage(); } } if (growTallGrass && upblock.BlockMaterial == EnumBlockMaterial.Air) { double rnd = rand.NextDouble() * totalWeedChance; for (int i = 0; i < weedNames.Length; i++) { rnd -= weedNames[i].Chance; if (rnd <= 0) { Block weedsBlock = Api.World.GetBlock(weedNames[i].Code); if (weedsBlock != null) { Api.World.BlockAccessor.SetBlock(weedsBlock.BlockId, upPos); } break; } } } UpdateFarmlandBlock(); Api.World.BlockAccessor.MarkBlockEntityDirty(Pos); }