void TickAction() { var fadeOff = Tools.PheromoneFadeoff(); var agitatedFadeoff = fadeOff / 4; var checkSmashableFadeoff = agitatedFadeoff / 2; var zombie = (Zombie)pawn; if (zombie.state == ZombieState.Emerging) { return; } var map = zombie.Map; if (zombie.Dead || zombie.Destroyed) { EndJobWith(JobCondition.InterruptForced); return; } if (zombie.state == ZombieState.ShouldDie) { EndJobWith(JobCondition.InterruptForced); zombie.Kill(null); return; } if (ZombieSettings.Values.zombiesDieVeryEasily) { if (zombie.health.hediffSet.GetHediffs <Hediff_Injury>().Any()) { zombie.Kill(null); return; } } if (zombie.Downed) { if (ZombieSettings.Values.zombiesDieVeryEasily || ZombieSettings.Values.doubleTapRequired == false) { zombie.Kill(null); return; } var walkCapacity = PawnCapacityUtility.CalculateCapacityLevel(zombie.health.hediffSet, PawnCapacityDefOf.Moving); var missingBrain = zombie.health.hediffSet.GetBrain() == null; if (walkCapacity < 0.25f || missingBrain) { zombie.Kill(null); return; } var injuries = zombie.health.hediffSet.GetHediffs <Hediff_Injury>(); foreach (var injury in injuries) { if (ZombieSettings.Values.zombiesDieVeryEasily) { zombie.Kill(null); return; } if (injury.IsOld() == false) { injury.Heal(injury.Severity + 0.5f); break; } } if (zombie.Downed) { return; } } // handling invalid destinations // if (destination.x == 0 && destination.z == 0) { destination = IntVec3.Invalid; } if (zombie.HasValidDestination(destination)) { return; } // if we are near targets then attack them // var enemy = CanAttack(); if (enemy != null) { destination = enemy.Position; zombie.state = ZombieState.Tracking; if (Constants.USE_SOUND) { var info = SoundInfo.InMap(enemy); SoundDef.Named("ZombieHit").PlayOneShot(info); } AttackThing(enemy, JobDefOf.AttackMelee); return; } // eat a downed or dead pawn // if (eatTarget == null) { eatTarget = CanIngest(out eatTargetIsCorpse); if (eatTarget != null) { eatTargetDied = eatTarget.Dead; } } if (eatTarget != null) { if (eatDelayCounter == 0) { if (eatTarget != lastEatTarget) { lastEatTarget = eatTarget; zombie.Drawer.rotator.FaceCell(eatTarget.Position); var zombieLeaner = zombie.Drawer.leaner as ZombieLeaner; if (zombieLeaner != null) { zombieLeaner.extraOffset = (eatTarget.Position.ToVector3() - zombie.Position.ToVector3()) * 0.5f; } Tools.CastThoughtBubble(pawn, Constants.EATING); } CastEatingSound(); } eatDelayCounter++; if (eatDelayCounter <= EatDelay) { return; } eatDelayCounter = 0; var bodyPartRecord = FirstEatablePart(eatTarget); if (bodyPartRecord != null) { var hediff_MissingPart = (Hediff_MissingPart)HediffMaker.MakeHediff(HediffDefOf.MissingBodyPart, eatTarget, bodyPartRecord); hediff_MissingPart.lastInjury = HediffDefOf.Bite; hediff_MissingPart.IsFresh = true; eatTarget.health.AddHediff(hediff_MissingPart, null, null); if (eatTargetIsCorpse == false && eatTargetDied == false && eatTarget.Dead) { Tools.DoWithAllZombies(map, z => { if (z.jobs != null) { var driver = z.jobs.curDriver as JobDriver_Stumble; if (driver != null && driver.eatTarget == eatTarget) { driver.eatTargetDied = true; driver.eatTargetIsCorpse = true; } } }); if (PawnUtility.ShouldSendNotificationAbout(eatTarget) && eatTarget.RaceProps.Humanlike) { Messages.Message("MessageEatenByPredator".Translate(new object[] { eatTarget.LabelShort, zombie.LabelIndefinite() }).CapitalizeFirst(), zombie, MessageSound.Negative); } eatTarget.Strip(); } return; } else { var corpse = map.thingGrid .ThingsListAt(eatTarget.Position) .OfType <Corpse>() .FirstOrDefault(c => c.InnerPawn == eatTarget); if (corpse != null) { corpse.Destroy(DestroyMode.Vanish); } Tools.DoWithAllZombies(map, z => { if (z.jobs != null) { var driver = z.jobs.curDriver as JobDriver_Stumble; if (driver != null && driver.eatTarget == eatTarget) { driver.eatTarget = null; driver.lastEatTarget = null; driver.eatDelayCounter = 0; } } }); } } else { var zombieLeaner = zombie.Drawer.leaner as ZombieLeaner; if (zombieLeaner != null) { zombieLeaner.extraOffset = Vector3.zero; } } var basePos = zombie.Position; // calculate possible moves, sort by pheromone value and take top 3 // then choose the one with the lowest zombie count // also, emit a circle of timestamps when discovering a pheromone // trace so nearby zombies pick it up too (leads to a chain reaction) // var grid = zombie.Map.GetGrid(); var possibleTrackingMoves = new List <IntVec3>(); var currentTicks = Tools.Ticks(); var timeDelta = long.MaxValue; for (int i = 0; i < 8; i++) { var pos = basePos + GenAdj.AdjacentCells[i]; if (currentTicks - grid.Get(pos, false).timestamp < fadeOff && zombie.HasValidDestination(pos)) { possibleTrackingMoves.Add(pos); } } if (possibleTrackingMoves.Count > 0) { possibleTrackingMoves.Sort((p1, p2) => SortByTimestamp(grid, p1, p2)); possibleTrackingMoves = possibleTrackingMoves.Take(Constants.NUMBER_OF_TOP_MOVEMENT_PICKS).ToList(); possibleTrackingMoves = possibleTrackingMoves.OrderBy(p => grid.Get(p, false).zombieCount).ToList(); var nextMove = possibleTrackingMoves.First(); timeDelta = currentTicks - grid.Get(nextMove, false).timestamp; destination = nextMove; if (zombie.state == ZombieState.Wandering) { Tools.ChainReact(zombie.Map, basePos, nextMove); if (timeDelta <= agitatedFadeoff) { CastBrainzThought(); } } zombie.state = ZombieState.Tracking; } if (destination.IsValid == false) { zombie.state = ZombieState.Wandering; } bool checkSmashable = timeDelta >= checkSmashableFadeoff; if (ZombieSettings.Values.smashOnlyWhenAgitated) { checkSmashable &= zombie.state == ZombieState.Tracking; } if (destination.IsValid == false || checkSmashable) { var building = CanSmash(); if (building != null) { destination = building.Position; if (Constants.USE_SOUND) { var info = SoundInfo.InMap(enemy); SoundDef.Named("ZombieHit").PlayOneShot(info); } AttackThing(building, JobDefOf.AttackStatic); return; } } if (destination.IsValid == false) { var hour = GenLocalDate.HourOfDay(Find.VisibleMap); // check for day/night and dust/dawn // var moveTowardsCenter = false; if (map.areaManager.Home[basePos] == false) { if (hour < 12) { hour += 24; } if (hour > Constants.HOUR_START_OF_NIGHT && hour < Constants.HOUR_END_OF_NIGHT) { moveTowardsCenter = true; } else if (hour >= Constants.HOUR_START_OF_DUSK && hour <= Constants.HOUR_START_OF_NIGHT) { moveTowardsCenter = Rand.RangeInclusive(hour, Constants.HOUR_START_OF_NIGHT) == Constants.HOUR_START_OF_NIGHT; } else if (hour >= Constants.HOUR_END_OF_NIGHT && hour <= Constants.HOUR_START_OF_DAWN) { moveTowardsCenter = Rand.RangeInclusive(Constants.HOUR_END_OF_NIGHT, hour) == Constants.HOUR_END_OF_NIGHT; } } var possibleMoves = new List <IntVec3>(); for (int i = 0; i < 8; i++) { var pos = basePos + GenAdj.AdjacentCells[i]; if (zombie.HasValidDestination(pos)) { possibleMoves.Add(pos); } } if (possibleMoves.Count > 0) { // during night, zombies drift towards the colonies center // if (moveTowardsCenter) { var center = zombie.wanderDestination.IsValid ? zombie.wanderDestination : map.Center; possibleMoves.Sort((p1, p2) => SortByDirection(center, p1, p2)); possibleMoves = possibleMoves.Take(Constants.NUMBER_OF_TOP_MOVEMENT_PICKS).ToList(); possibleMoves = possibleMoves.OrderBy(p => grid.Get(p, false).zombieCount).ToList(); destination = possibleMoves.First(); } else { // otherwise they sometimes stand or walk towards a random direction // if (Rand.Chance(Constants.STANDING_STILL_CHANCE)) { var n = possibleMoves.Count(); destination = possibleMoves[Constants.random.Next(n)]; } } } } // if we have a valid destination, go there // if (destination.IsValid) { MoveToCell(destination); } }