// smash nearby build stuff ================================================================= // public static bool Smash(this JobDriver_Stumble driver, Zombie zombie, bool checkSmashable, bool onylWhenNotRaging) { if (zombie.wasMapPawnBefore == false && zombie.IsSuicideBomber == false && zombie.IsTanky == false) { if (driver.destination.IsValid && checkSmashable == false) { return(false); } if (onylWhenNotRaging && zombie.raging > 0) { return(false); } } var building = CanSmash(zombie); if (building == null) { return(false); } driver.destination = building.Position; if (Constants.USE_SOUND && Prefs.VolumeAmbient > 0f) { var info = SoundInfo.InMap(building); SoundDef.Named("ZombieHit").PlayOneShot(info); } AttackThing(zombie, building, JobDefOf.AttackStatic); return(true); }
// static bool LeanAndDelay(this JobDriver_Stumble driver, Zombie zombie, Pawn eatTargetPawn) { if (driver.eatDelayCounter == 0) { if (eatTargetPawn != driver.lastEatTarget) { driver.lastEatTarget = eatTargetPawn; zombie.rotationTracker.FaceCell(driver.eatTarget.Position); if (zombie.Drawer.leaner is ZombieLeaner zombieLeaner) { var offset = (driver.eatTarget.Position.ToVector3() - zombie.Position.ToVector3()) * 0.5f; if (offset.magnitude < 1f) { zombieLeaner.extraOffset = offset; } } Tools.CastThoughtBubble(zombie, Constants.EATING); } CastEatingSound(zombie); } driver.eatDelayCounter++; if (driver.eatDelayCounter <= EatDelay(driver, zombie)) { return(true); } driver.eatDelayCounter = 0; zombie.raging = 0; return(false); }
// invalidate destination if necessary ====================================================== // public static bool ValidDestination(this JobDriver_Stumble driver, Zombie zombie) { // find out if we still need to check for 0,0 as an invalid location if (driver.destination.x == 0 && driver.destination.z == 0) { driver.destination = IntVec3.Invalid; } return(zombie.HasValidDestination(driver.destination)); }
// if we have a valid destination, go there ================================================= // public static void ExecuteMove(this JobDriver_Stumble driver, Zombie zombie, PheromoneGrid grid) { if (driver.destination.IsValid) { grid.ChangeZombieCount(zombie.lastGotoPosition, -1); grid.ChangeZombieCount(driver.destination, 1); zombie.lastGotoPosition = driver.destination; zombie.pather.StartPath(driver.destination, PathEndMode.OnCell); } }
public static bool Mine(this JobDriver_Stumble driver, Zombie zombie, bool allDirections = false) { if (zombie.miningCounter > 0) { zombie.miningCounter--; return(true); } var map = zombie.Map; var basePos = zombie.Position; var delta = (zombie.wanderDestination.IsValid ? zombie.wanderDestination : zombie.Map.Center) - basePos; var idx = Tools.CellsAroundIndex(delta); if (idx == -1) { return(false); } var adjacted = GenAdj.AdjacentCellsAround; var cells = allDirections ? adjacted.ToList() : new List <IntVec3>() { adjacted[idx], adjacted[(idx + 1) % 8], adjacted[(idx + 7) % 8] }; var mineable = cells .Select(c => basePos + c) .Where(c => c.InBounds(map)) .Select(c => c.GetFirstThing <Mineable>(map)) .FirstOrDefault(); if (mineable == null) { return(false); } zombie.rotationTracker.FaceCell(mineable.Position); effecter.Trigger(zombie, mineable); var baseDamage = (int)GenMath.LerpDouble(1, 5, 1, 10, Math.Max(1, Tools.StoryTellerDifficulty)); var damage = (!mineable.def.building.isNaturalRock) ? baseDamage : baseDamage * 2; if (mineable.HitPoints > damage) { mineable.TakeDamage(new DamageInfo(DamageDefOf.Mining, damage)); } else { mineable.Destroy(DestroyMode.KillFinalize); } zombie.miningCounter = (int)GenMath.LerpDouble(1, 5, 180, 90, Math.Max(1, Tools.StoryTellerDifficulty)); return(true); }
// make zombies die if necessary ============================================================ // public static bool ShouldDie(this JobDriver_Stumble driver, Zombie zombie) { if (zombie.Dead || zombie.Spawned == false) { driver.EndJobWith(JobCondition.InterruptForced); return(true); } if (zombie.state == ZombieState.ShouldDie) { driver.EndJobWith(JobCondition.InterruptForced); zombie.Kill(null); return(true); } if (zombie.IsSuicideBomber) { if (zombie.bombWillGoOff && zombie.EveryNTick(NthTick.Every10)) { zombie.bombTickingInterval -= 2f; } if (zombie.bombTickingInterval <= 0f) { zombie.Kill(null); return(true); } } if (zombie.EveryNTick(NthTick.Every10)) { if (ZombieSettings.Values.zombiesDieVeryEasily) { if (zombie.hasTankySuit <= 0f && zombie.health.hediffSet.GetHediffs <Hediff_Injury>().Any()) { zombie.Kill(null); return(true); } } else { var hediffs = zombie.health.hediffSet.hediffs .Where(hediff => hediff.def == HediffDefOf.WoundInfection) .ToArray(); foreach (var hediff in hediffs) { zombie.health.RemoveHediff(hediff); } } } return(false); }
// during night, drift towards colony ======================================================= // public static void Wander(this JobDriver_Stumble driver, Zombie zombie, PheromoneGrid grid, List <IntVec3> possibleMoves) { if (driver.destination.IsValid) { return; } // check for day/night and dust/dawn // during night, zombies drift towards the colonies center // if (zombie.Map.areaManager.Home[zombie.Position] == false) { var moveTowardsCenter = false; var hour = GenLocalDate.HourOfDay(Find.CurrentMap); 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; } if (moveTowardsCenter) { var center = zombie.wanderDestination.IsValid ? zombie.wanderDestination : zombie.Map.Center; possibleMoves.Sort((p1, p2) => p1.DistanceToSquared(center).CompareTo(p2.DistanceToSquared(center))); possibleMoves = possibleMoves.Take(Constants.NUMBER_OF_TOP_MOVEMENT_PICKS).ToList(); possibleMoves = possibleMoves.OrderBy(p => grid.GetZombieCount(p)).ToList(); driver.destination = possibleMoves.First(); return; } } // random wandering var n = possibleMoves.Count(); driver.destination = possibleMoves[Constants.random.Next(n)]; }
// static bool EatBodyPart(this JobDriver_Stumble driver, Zombie zombie, Pawn eatTargetPawn) { var bodyPartRecord = FirstEatablePart(eatTargetPawn); if (bodyPartRecord == null) { driver.eatTarget.Destroy(DestroyMode.Vanish); return(false); } var eatTargetAlive = driver.eatTarget is Pawn && ((Pawn)driver.eatTarget).Dead == false; var hediff_MissingPart = (Hediff_MissingPart)HediffMaker.MakeHediff(HediffDefOf.MissingBodyPart, eatTargetPawn, bodyPartRecord); hediff_MissingPart.lastInjury = HediffDefOf.Bite; hediff_MissingPart.IsFresh = true; eatTargetPawn.health.AddHediff(hediff_MissingPart, null, null); var eatTargetStillAlive = driver.eatTarget is Pawn && ((Pawn)driver.eatTarget).Dead == false; if (eatTargetAlive && eatTargetStillAlive == false) { if (PawnUtility.ShouldSendNotificationAbout(eatTargetPawn) && eatTargetPawn.RaceProps.Humanlike) { MethodInfo translate; object[] parameters; var type = AccessTools.TypeByName("Verse.TranslatorFormattedStringExtensions"); if (type != null) { // 1.0 translate = AccessTools.Method(type, "Translate", new Type[] { typeof(string), typeof(NamedArgument), typeof(NamedArgument), typeof(NamedArgument) }); parameters = new object[] { "MessageEatenByPredator", new NamedArgument(driver.eatTarget.LabelShort, null), zombie.LabelIndefinite().Named("PREDATOR"), driver.eatTarget.Named("EATEN") }; } else { // B19 translate = AccessTools.Method(type, "Translate", new Type[] { typeof(string), typeof(object[]) }); parameters = new object[] { "MessageEatenByPredator", new object[] { driver.eatTarget.LabelShort, zombie.LabelIndefinite() } }; } var msg = (string)translate.Invoke(null, parameters); Messages.Message(msg.CapitalizeFirst(), zombie, MessageTypeDefOf.NegativeEvent); } eatTargetPawn.Strip(); } return(true); }
// lean in and eat bodies made out of flesh ================================================= // public static bool Eat(this JobDriver_Stumble driver, Zombie zombie, PheromoneGrid grid) { if (zombie.hasTankyShield != -1f || zombie.hasTankyHelmet != -1f || zombie.hasTankySuit != -1f) { return(false); } if (driver.eatTarget != null && driver.eatTarget.Spawned == false) { driver.eatTarget = null; driver.lastEatTarget = null; driver.eatDelayCounter = 0; } if (driver.eatTarget == null && grid.GetZombieCount(zombie.Position) <= 2) { driver.eatTarget = CanIngest(zombie); } var eatTargetPawn = driver.eatTarget as Pawn ?? (driver.eatTarget as Corpse)?.InnerPawn; if (eatTargetPawn != null) { if (driver.LeanAndDelay(zombie, eatTargetPawn)) { return(true); } if (driver.EatBodyPart(zombie, eatTargetPawn)) { return(true); } } else { if (zombie.Drawer.leaner is ZombieLeaner zombieLeaner) { zombieLeaner.extraOffset = Vector3.zero; } } return(false); }
// calculate possible moves ================================================================= // public static List <IntVec3> PossibleMoves(this JobDriver_Stumble driver, Zombie zombie) { if (driver.destination.IsValid) { return(new List <IntVec3>()); } var result = new List <IntVec3>(8); var pos = zombie.Position; foreach (var vec in GenAdj.AdjacentCells) { var cell = pos + vec; if (zombie.HasValidDestination(cell)) { result.Add(cell); } } return(result); }
static int EatDelay(this JobDriver_Stumble driver, Zombie zombie) { if (driver.eatDelay == 0) { driver.eatDelay = Constants.EAT_DELAY_TICKS; var bodyType = zombie.story.bodyType; if (bodyType == BodyTypeDefOf.Thin) { driver.eatDelay *= 3; } else if (bodyType == BodyTypeDefOf.Hulk) { driver.eatDelay /= 2; } else if (bodyType == BodyTypeDefOf.Fat) { driver.eatDelay /= 4; } } return(driver.eatDelay); }
// attack nearby enemies ==================================================================== // public static bool Attack(this JobDriver_Stumble driver, Zombie zombie) { var enemy = CanAttack(zombie); if (enemy == null) { return(false); } driver.destination = enemy.Position; zombie.state = ZombieState.Tracking; if (Constants.USE_SOUND && Prefs.VolumeAmbient > 0f) { var info = SoundInfo.InMap(enemy); SoundDef.Named("ZombieHit").PlayOneShot(info); } AttackThing(zombie, enemy, JobDefOf.AttackMelee); return(true); }
// use rage grid to get to colonists ======================================================== // public static bool RageMove(this JobDriver_Stumble driver, Zombie zombie, PheromoneGrid grid, List <IntVec3> possibleMoves, bool checkSmashable) { var info = ZombieWanderer.GetMapInfo(zombie.Map); var newPos = info.GetParent(zombie.Position, false); if (newPos.IsValid == false) { // tanky can get directly through walls if (zombie.IsTanky) { newPos = info.GetParent(zombie.Position, true); } if (newPos.IsValid == false) { // no next move available zombie.raging = 0; return(Smash(driver, zombie, checkSmashable, false)); } } // next tanky move is on a building if (newPos.GetEdifice(zombie.Map) is Building building && (building as Mineable) == null) { return(Smash(driver, zombie, checkSmashable, false)); } // next move is on a door if (newPos.GetEdifice(zombie.Map) is Building_Door door) { if (door.Open) { driver.destination = newPos; return(false); } return(Smash(driver, zombie, checkSmashable, false)); } // move into places where there is max 0/1 zombie already var destZombieCount = grid.GetZombieCount(newPos); if (destZombieCount < (zombie.IsTanky ? 1 : 2)) { driver.destination = newPos; return(false); } // cannot move? lets smash things if (Smash(driver, zombie, checkSmashable, false)) { return(true); } // cannot smash? look for alternative ways to move orthogonal if (TryToDivert(ref newPos, grid, zombie.Position, possibleMoves)) { driver.destination = newPos; return(false); } // move to least populated place var zCount = possibleMoves.Select(p => grid.GetZombieCount(p)).Min(); driver.destination = possibleMoves.Where(p => grid.GetZombieCount(p) == zCount).RandomElement(); return(false); }
public static bool Track(this JobDriver_Stumble driver, Zombie zombie, PheromoneGrid grid) { if (zombie.EveryNTick(NthTick.Every60) || fadeOff == -1) { fadeOff = Tools.PheromoneFadeoff(); wasColonistFadeoff = fadeOff / 6; agitatedFadeoff = fadeOff / 4; checkSmashableFadeoff1 = agitatedFadeoff / 4; checkSmashableFadeoff2 = agitatedFadeoff * 3 / 4; } var trackingMoves = new List <IntVec3>(8); var currentTicks = Tools.Ticks(); var timeDelta = long.MaxValue; var fmin = long.MaxValue; if (zombie.raging == 0) { for (var i = 0; i < 8; i++) { var pos = zombie.Position + GenAdj.AdjacentCells[i]; if (zombie.HasValidDestination(pos)) { var f = zombie.wasMapPawnBefore ? wasColonistFadeoff : fadeOff; var tdiff = currentTicks - grid.GetTimestamp(pos); fmin = Math.Min(fmin, tdiff); if (tdiff < f) { trackingMoves.Add(pos); } } } } if (trackingMoves.Count > 0) { trackingMoves.Sort((p1, p2) => grid.GetTimestamp(p2).CompareTo(grid.GetTimestamp(p1))); trackingMoves = trackingMoves.Take(Constants.NUMBER_OF_TOP_MOVEMENT_PICKS).ToList(); trackingMoves = trackingMoves.OrderBy(p => grid.GetZombieCount(p)).ToList(); var nextMove = trackingMoves.First(); timeDelta = currentTicks - (grid.GetTimestamp(nextMove)); driver.destination = nextMove; if (zombie.state == ZombieState.Wandering) { Tools.ChainReact(zombie.Map, zombie.Position, nextMove); if (timeDelta <= agitatedFadeoff) { CastBrainzThought(zombie); } } zombie.state = ZombieState.Tracking; } if (driver.destination.IsValid == false) { zombie.state = ZombieState.Wandering; } if (zombie.wasMapPawnBefore) { return(true); } var checkSmashable = timeDelta >= checkSmashableFadeoff1 && timeDelta < checkSmashableFadeoff2; if (ZombieSettings.Values.smashOnlyWhenAgitated) { checkSmashable &= (zombie.state == ZombieState.Tracking || zombie.raging > 0); } return(checkSmashable); }