public virtual void OnNPCAtJob(BlockJobInstance blockJobInstance, ref NPCState state) { ScientistJobInstance instance = blockJobInstance as ScientistJobInstance; if (instance == null) { state.JobIsDone = true; return; } Colony owner = instance.Owner; instance.NPC.LookAt(instance.Position.Vector); ColonyScienceState scienceData = owner.ScienceData; state.JobIsDone = true; // if no stored items - check if the stockpile contains the items required for any of the researches var completedCycles = scienceData.CompletedCycles; if (instance.StoredItemCount <= 0) { if (completedCycles.Count == 0) { // done for now state.SetIndicator(new Shared.IndicatorState(Random.NextFloat(8f, 16f), BuiltinBlocks.Indices.erroridle)); return; } int possibles = 0; for (int i = 0; i < completedCycles.Count; i++) { ScienceKey cyclesResearch = completedCycles.GetKeyAtIndex(i); if (cyclesResearch.Researchable.TryGetCyclesCondition(out ScientistCyclesCondition cyclesData) && completedCycles.GetValueAtIndex(i) < cyclesData.CycleCount) { possibles++; if (owner.Stockpile.Contains(cyclesData.ItemsPerCycle)) { instance.ShouldTakeItems = true; state.SetCooldown(0.3); return; // found an in-progress research that we have items for } } } // missing items, find random requirement to use as indicator if (possibles > 0) { int possiblesIdx = Random.Next(0, possibles); for (int i = 0; i < completedCycles.Count; i++) { ScienceKey cyclesResearch = completedCycles.GetKeyAtIndex(i); if (cyclesResearch.Researchable.TryGetCyclesCondition(out ScientistCyclesCondition cyclesData) && completedCycles.GetValueAtIndex(i) < cyclesData.CycleCount) { if (possiblesIdx-- <= 0) { MissingItemHelper(cyclesData.ItemsPerCycle, owner.Stockpile, ref state); return; } } } // should be unreachable } // done for now state.SetIndicator(new Shared.IndicatorState(Random.NextFloat(8f, 16f), BuiltinBlocks.Indices.erroridle)); return; } // have stored items, try to forward an active science if (completedCycles.Count == 0) { instance.StoredItemCount = 0; state.SetCooldown(0.3); return; } var happyData = owner.HappinessData; float cyclesToAdd = happyData.ScienceSpeedMultiplierCalculator.GetSpeedMultiplier(happyData.CachedHappiness, instance.NPC); int doneScienceIndex = -1; const float MINIMUM_CYCLES_TO_ADD = 0.001f; if (cyclesToAdd <= MINIMUM_CYCLES_TO_ADD) { state.SetIndicator(new Shared.IndicatorState(CraftingCooldown, BuiltinBlocks.Indices.missingerror)); Log.WriteWarning($"Cycles below minimum for science job at {instance.Position}!"); return; } for (int i = 0; i < completedCycles.Count && cyclesToAdd >= 0f; i++) { AbstractResearchable research = completedCycles.GetKeyAtIndex(i).Researchable; float progress = completedCycles.GetValueAtIndex(i); if (!research.TryGetCyclesCondition(out ScientistCyclesCondition cyclesCondition) || progress >= cyclesCondition.CycleCount) { continue; } bool atCycleBoundary = Math.Abs(progress - Math.RoundToInt(progress)) < MINIMUM_CYCLES_TO_ADD; float freeCycles = (float)System.Math.Ceiling(progress) - progress; if (!atCycleBoundary) { if (cyclesToAdd <= freeCycles) { OnDoFullCycles(ref state); // cycles <= freecycles, just add all return; } else { OnDoPartialCycles(ref state, Math.Min(freeCycles, cyclesToAdd)); } } if (progress >= cyclesCondition.CycleCount) { // completed on a partial cycle (it wasn't completed a few lines up) continue; } // at boundary and/or will cross a boundary with the cyclesToAdd List <InventoryItem> requirements = cyclesCondition.ItemsPerCycle; if (owner.Stockpile.TryRemove(requirements)) { // got the items, deal with recycling, then just add the full cyclesToAdd int recycled = 0; for (int j = 0; j < requirements.Count; j++) { ushort type = requirements[j].Type; if (type == BuiltinBlocks.Indices.sciencebaglife || type == BuiltinBlocks.Indices.sciencebagbasic || type == BuiltinBlocks.Indices.sciencebagmilitary ) { recycled += requirements[j].Amount; } } for (int j = recycled; j > 0; j--) { if (Random.NextDouble() > RECYCLE_CHANCE) { recycled--; } } if (recycled > 0) { owner.Stockpile.Add(BuiltinBlocks.Indices.linenbag, recycled); } OnDoFullCycles(ref state); return; } continue; // unreachable void OnDoFullCycles(ref NPCState stateCopy) { scienceData.CyclesAddProgress(research.AssignedKey, cyclesToAdd); stateCopy.SetIndicator(new Shared.IndicatorState(CraftingCooldown, NPCIndicatorType.Science, (ushort)research.AssignedKey.Index)); instance.StoredItemCount--; progress += cyclesToAdd; if (progress >= cyclesCondition.CycleCount && research.AreConditionsMet(scienceData)) { SendCompleteMessage(); } } void OnDoPartialCycles(ref NPCState stateCopy, float cyclesToUse) { cyclesToAdd -= cyclesToUse; scienceData.CyclesAddProgress(research.AssignedKey, cyclesToUse); progress += cyclesToUse; if (progress >= cyclesCondition.CycleCount && research.AreConditionsMet(scienceData)) { SendCompleteMessage(); } doneScienceIndex = (int)research.AssignedKey.Index; } void SendCompleteMessage() { Players.Player[] owners = owner.Owners; for (int j = 0; j < owners.Length; j++) { Players.Player player = owners[j]; if (player.ConnectionState == Players.EConnectionState.Connected && player.ActiveColony == owner) { string msg = Localization.GetSentence(player.LastKnownLocale, "chat.onscienceready"); string res = Localization.GetSentence(player.LastKnownLocale, research.GetKey() + ".name"); msg = string.Format(msg, res); Chatting.Chat.Send(player, msg); } } } } if (doneScienceIndex >= 0) { // did some freecoasting state.SetIndicator(new Shared.IndicatorState(CraftingCooldown, NPCIndicatorType.Science, (ushort)doneScienceIndex)); instance.StoredItemCount--; return; } // had stored items, but for some reason couldn't dump them in existing research // reset the job basically instance.StoredItemCount = 0; state.SetCooldown(0.3); return; void MissingItemHelper(IList <InventoryItem> requirements, Stockpile stockpile, ref NPCState stateCopy) { ushort missing = 0; for (int i = 0; i < requirements.Count; i++) { if (!stockpile.Contains(requirements[i])) { missing = requirements[i].Type; break; } } float cooldown = Random.NextFloat(8f, 16f); stateCopy.SetIndicator(new Shared.IndicatorState(cooldown, missing, true, false)); stateCopy.JobIsDone = false; } }
public virtual void OnNPCAtJob(BlockJobInstance blockJobInstance, ref NPCState state) { ConstructionJobInstance instance = (ConstructionJobInstance)blockJobInstance; if (BlockTypes.ContainsByReference(instance.BlockType, out int index)) { Vector3 rotate = instance.NPC.Position.Vector; switch (index) { case 1: rotate.x -= 1f; break; case 2: rotate.x += 1f; break; case 3: rotate.z -= 1f; break; case 4: rotate.z += 1f; break; } instance.NPC.LookAt(rotate); } if (instance.ConstructionArea != null && !instance.ConstructionArea.IsValid) { instance.ConstructionArea = null; } if (instance.ConstructionArea == null) { if (AreaJobTracker.ExistingAreaAt(instance.Position.Add(-1, -1, -1), instance.Position.Add(1, 1, 1), out List <IAreaJob> jobs)) { for (int i = 0; i < jobs.Count; i++) { if (jobs[i] is ConstructionArea neighbourArea) { instance.ConstructionArea = neighbourArea; break; } } AreaJobTracker.AreaJobListPool.Return(jobs); } if (instance.ConstructionArea == null) { if (instance.DidAreaPresenceTest) { state.SetCooldown(0.5); ServerManager.TryChangeBlock(instance.Position, instance.BlockType, BuiltinBlocks.Types.air, instance.Owner); } else { state.SetIndicator(new Shared.IndicatorState(Random.NextFloat(3f, 5f), BuiltinBlocks.Indices.erroridle)); instance.DidAreaPresenceTest = true; } return; } } Assert.IsNotNull(instance.ConstructionArea); instance.ConstructionArea.DoJob(instance, ref state); }
public virtual void OnNPCAtJob(BlockJobInstance blockJobInstance, ref NPCState state) { state.JobIsDone = true; MinerJobInstance instance = (MinerJobInstance)blockJobInstance; if (instance.BlockTypeBelow == null || instance.BlockTypeBelow == BuiltinBlocks.Types.air) { if (World.TryGetTypeAt(instance.Position.Add(0, -1, 0), out ItemTypes.ItemType foundType)) { if (foundType == BuiltinBlocks.Types.air) { ThreadManager.InvokeOnMainThread(() => ServerManager.TryChangeBlock(instance.Position, instance.BlockType, BuiltinBlocks.Types.air, instance.Owner)); state.SetCooldown(3.0); // I don't know what's going on here, floating miner jobs return; } instance.BlockTypeBelow = foundType; } else { state.SetCooldown(5.0); return; } } if (instance.MiningCooldown <= 0f) { float cooldown = 0f; if (instance.BlockTypeBelow.CustomDataNode?.TryGetAs("minerMiningTime", out cooldown) ?? false) { instance.MiningCooldown = cooldown; } if (instance.MiningCooldown <= 0f) { ThreadManager.InvokeOnMainThread(() => ServerManager.TryChangeBlock(instance.Position, instance.BlockType, BuiltinBlocks.Types.air, instance.Owner)); state.SetCooldown(3.0); // loaded block below, but it turned out to be non-mineable return; } } if (BlockTypes.ContainsByReference(instance.BlockType, out int index)) { Vector3 rotate = instance.NPC.Position.Vector; switch (index) { case 1: rotate.x += 1f; break; case 2: rotate.x -= 1f; break; case 3: rotate.z += 1f; break; case 4: rotate.z -= 1f; break; } instance.NPC.LookAt(rotate); } AudioManager.SendAudio(instance.Position.Vector, "stoneDelete"); GatherResults.Clear(); var itemList = instance.BlockTypeBelow.OnRemoveItems; for (int i = 0; i < itemList.Count; i++) { GatherResults.Add(itemList[i]); } ModLoader.Callbacks.OnNPCGathered.Invoke(instance, instance.Position.Add(0, -1, 0), GatherResults); InventoryItem toShow = ItemTypes.ItemTypeDrops.GetWeightedRandom(GatherResults); if (toShow.Amount > 0) { state.SetIndicator(new Shared.IndicatorState(instance.MiningCooldown, toShow.Type)); } else { state.SetCooldown(instance.MiningCooldown); } state.Inventory.Add(GatherResults); instance.GatheredItemCount++; if (instance.GatheredItemCount >= MaxCraftsPerRun) { instance.ShouldTakeItems = true; } }