public void SummonMob() { if (FieldIdMove == Constants.InvalidMap) { return; } if (!DataProvider.Maps.TryGetValue(FieldIdMove, out var field)) { return; } if (field.Limitations.HasFlag(FieldLimit.SummonLimit)) { return; } if (!DataProvider.Items.TryGetValue(GetMobItemID, out var itemData)) { return; } Program.MainForm.LogAppend("Spawning mobs for contimove trip on map " + field.ID); var fh = field.GetFootholdUnderneath(MobSpawnPoint.X, MobSpawnPoint.Y, out var maxY); foreach (var itemDataSummon in itemData.Summons) { if (Rand32.Next() % 100 >= itemDataSummon.Chance) { continue; } field.SpawnMobWithoutRespawning(itemDataSummon.MobID, MobSpawnPoint, (byte)(fh.HasValue ? fh.Value.ID : 0)); } }
public static void HandleInteraction(Character chr, Packet packet) { var petItem = chr.GetSpawnedPet(); if (petItem == null) { return; } bool success = false; double multiplier = 1.0; // 4A 00 00 byte doMultiplier = packet.ReadByte(); if (doMultiplier != 0 && Pet.IsNamedPet(petItem)) { multiplier = 1.5; } byte interactionId = packet.ReadByte(); // dunno lol if (!DataProvider.Pets.TryGetValue(petItem.ItemID, out var petData) || !petData.Reactions.TryGetValue(interactionId, out var petReactionData)) { return; } long timeSinceLastInteraction = MasterThread.CurrentTime - chr.PetLastInteraction; // shouldnt be able to do this yet. if (petReactionData.LevelMin > petItem.Level || petReactionData.LevelMax < petItem.Level || timeSinceLastInteraction < 15000) { goto send_response; } // sick math chr.PetLastInteraction = MasterThread.CurrentTime; double additionalSucceedProbability = (((timeSinceLastInteraction - 15000.0) / 10000.0) * 0.01 + 1.0) * multiplier; var random = Rand32.Next() % 100; if (random >= (petReactionData.Prob * additionalSucceedProbability) || petItem.Fullness < 50) { goto send_response; } success = true; Pet.IncreaseCloseness(chr, petItem, petReactionData.Inc); Pet.UpdatePet(chr, petItem); send_response: SendPetInteraction(chr, interactionId, success); }
private static short GetItemAmount(int ItemID, int Min, int Max) { var ItemType = ItemID / 1000000; if (Max > 0 && (ItemType == 2 || ItemType == 3 || ItemType == 4)) { return((short)(Min + Rand32.Next() % (Max - Min + 1))); } return(1); }
public static void OnStatChangeByMobSkill(Character chr, MobSkillLevelData msld, short delay = 0) { // See if we can actually set the effect... int prop = 100; if (msld.Prop != 0) { prop = msld.Prop; } if (Rand32.Next() % 100 >= prop) { return; // Luck. } BuffStat setStat = null; int rValue = msld.SkillID | (msld.Level << 16); var ps = chr.PrimaryStats; int nValue = 1; switch ((Constants.MobSkills.Skills)msld.SkillID) { case Constants.MobSkills.Skills.Seal: setStat = ps.BuffSeal; break; case Constants.MobSkills.Skills.Darkness: setStat = ps.BuffDarkness; break; case Constants.MobSkills.Skills.Weakness: setStat = ps.BuffWeakness; break; case Constants.MobSkills.Skills.Stun: setStat = ps.BuffStun; break; case Constants.MobSkills.Skills.Curse: setStat = ps.BuffCurse; break; case Constants.MobSkills.Skills.Poison: setStat = ps.BuffPoison; nValue = msld.X; break; } if (setStat != null && !setStat.IsSet()) { var buffTime = msld.Time * 1000; var stat = setStat.Set(rValue, (short)nValue, BuffStat.GetTimeForBuff(buffTime + delay)); if (stat != 0) { chr.Buffs.FinalizeBuff(stat, delay); } } }
public static void HandleUseSummonSack(Character chr, Packet packet) { short slot = packet.ReadShort(); int itemid = packet.ReadInt(); BaseItem item = chr.Inventory.GetItem(2, slot); if (item == null || item.ItemID != itemid || !DataProvider.Items.TryGetValue(itemid, out ItemData data)) { NoChange(chr); return; } if (data.Summons.Count == 0) { NoChange(chr); return; } if (chr.AssertForHack( chr.Inventory.TakeItemAmountFromSlot(Constants.getInventory(itemid), slot, 1, false) == null, "Tried to use summon sack while not having them (???)" )) { return; } foreach (var isi in data.Summons) { if (DataProvider.Mobs.ContainsKey(isi.MobID)) { if (Rand32.Next() % 100 < isi.Chance) { chr.Field.SpawnMobWithoutRespawning(isi.MobID, chr.Position, chr.Foothold, summonType: data.Type); } } else { Program.MainForm.LogAppend("Summon sack {0} has mobid that doesn't exist: {1}", itemid, isi.MobID); } } NoChange(chr); }
public void ResetEvent() { EventDoing = false; Event = GetMobItemID != 0 && (Rand32.Next() % 100) <= 30; int randomMinutes = (int)(Rand32.Next() % (RequiredMin - 5)); MobGenTime = NextBoardingTime + ((60 * TimeMultiplier) * (WaitMin + randomMinutes + 2)); var curTime = MasterThread.CurrentTime; var timeTillSpawn = MobGenTime - curTime; var timeTillBoard = NextBoardingTime - curTime; if (Event) { var txt = $"Will spawn crogs on trip {FieldIdStartShipMove} -> {FieldIdEndShipMove} (map {FieldIdMove}) in {(timeTillSpawn / 1000):D} seconds"; MessagePacket.SendNoticeGMs(txt, MessagePacket.MessageTypes.Notice); } }
public override void Prepare() { questions.Clear(); var page = DataProvider.QuizQuestions[(byte)(1 + Rand32.Next() % 7)]; while (questions.Count < 10) { var nextQuestion = page.RandomElement(); if (!questions.Contains(nextQuestion)) { questions.Add(nextQuestion); } } QuizMap.ChatEnabled = true; QuizMap.PortalsOpen = false; base.Prepare(); }
public override void OnHackDetected() { if (!Loaded || !HackDetected.HasValue) { return; } var character = Player.Character; var hack = HackDetected.Value; if (hack.HasFlag(RedisBackend.HackKind.Speedhack)) { MessagePacket.SendNoticeGMs( $"Detected speed hacks on character '{character.Name}', map {character.MapID}...", MessagePacket.MessageTypes.RedText); if (character.IsGM == false) { character.PermaBan( "Detected speedhack", extraDelay: (int)((2 * 60) + Rand32.Next() % (5 * 60)) ); } } if (hack.HasFlag(RedisBackend.HackKind.MemoryEdits)) { MessagePacket.SendNoticeGMs( $"Detected memory edits on character '{character.Name}', map {character.MapID}.", MessagePacket.MessageTypes.RedText ); if (character.IsGM == false) { // Add some randomness character.PermaBan( "Detected memory edits", // Between 2 and 12 minutes extraDelay: (int)((2 * 60) + Rand32.Next() % (10 * 60)) ); } } }
public static BaseItem CreateCashItem(LockerItem li, CommodityInfo ci) { li.CashId = (long)((long)(Rand32.Next()) << 32 | Rand32.Next()); li.CashId &= 0x00FFFFFFFFFFFFFF; // Get rid of the first byte var item = BaseItem.CreateFromItemID(li.ItemId); item.Amount = li.Amount; item.CashId = li.CashId; item.Expiration = li.Expiration; if (item is PetItem pi) { pi.Name = DataProvider.Pets[pi.ItemID].Name; pi.Closeness = 0; pi.Fullness = 100; pi.Level = 1; } return(item); }
public MobGenItem(Life life, long?currentTime) { ID = life.ID; Foothold = (short)life.Foothold; FacesLeft = life.FacesLeft; X = life.X; _initializedYAxis = false; _y = life.Y; _cy = life.Cy; MobCount = 0; if (currentTime == null) { currentTime = MasterThread.CurrentTime; } var regenInterval = RegenInterval = life.RespawnTime * 1000; if (regenInterval >= 0) { var T1 = regenInterval / 10; var T2 = 6 * regenInterval / 10; RegenAfter = currentTime.Value; if (T2 != 0) { RegenAfter += T1 + Rand32.Next() % T2; } //else // RegenAfter += Rand32.Next(); } else { RegenAfter = 0; } }
public static short GetVariation(short v, ItemVariation enOption) { if (v <= 0) { return(0); } if (enOption == ItemVariation.Gachapon) { // TODO: Gacha return(v); } // This logic has 2 bonus bits. int maxDiff = Math.Min(v / 10 + 1, 5); // Max stat // Maximum amount of bits to set // Note: // Default: 1 << (1 + 2) == 0x08 (3 bits) // Max: 1 << (5 + 2) == 0x80 (7 bits) uint maxBits = (uint)(1 << (maxDiff + 2)); int randBits = (int)(Rand32.Next() % maxBits); // Trace.WriteLine($"{(v11 >> 6) & 1} {(v11 >> 5) & 1} | {(v11 >> 4) & 1} {(v11 >> 3) & 1} {(v11 >> 2) & 1} {(v11 >> 1) & 1} {(v11 >> 0) & 1} "); // 0 - 3 range int calculatedBoost = 0 + ((randBits >> 4) & 1) + ((randBits >> 3) & 1) + ((randBits >> 2) & 1) + ((randBits >> 1) & 1) + ((randBits >> 0) & 1) // Additional bonus - 2 + ((randBits >> 5) & 1) + ((randBits >> 6) & 1); // Trace.WriteLine($"Boost w/ bonus: {calculatedBoost}"); // Make sure we don't give negative boost calculatedBoost = Math.Max(0, calculatedBoost); //Trace.WriteLine($"Actual boost: {calculatedBoost}"); // Normal is the only one that can go down. The rest goes up if (enOption == ItemVariation.Normal) { if ((Rand32.Next() & 1) == 0) { return((short)(v - calculatedBoost)); } else { return((short)(v + calculatedBoost)); } } else if (enOption == ItemVariation.Better) { if ((Rand32.Next() % 10) < 3) { return(v); } else { return((short)(v + calculatedBoost)); } } else if (enOption == ItemVariation.Great) { if ((Rand32.Next() % 10) < 1) { return(v); } else { return((short)(v + calculatedBoost)); } } else { throw new Exception("Invalid ItemVariation"); } }
public static void HandleMeleeAttack(Character chr, Packet packet) { //Program.MainForm.LogAppend("Handling Melee"); if (!ParseAttackData(chr, packet, out AttackData ad, AttackTypes.Melee)) { return; } SendMeleeAttack(chr, ad); Mob mob; bool died; int TotalDamage = 0; double MaxPossibleDamage; if (ad.SkillID != 0) { chr.Skills.UseMeleeAttack(ad.SkillID, ad); } bool pickPocketActivated = chr.PrimaryStats.HasBuff(Constants.ChiefBandit.Skills.Pickpocket); var pickPocketSLD = chr.Skills.GetSkillLevelData(Constants.ChiefBandit.Skills.Pickpocket, out byte pickPocketSkillLevel); bool pickOk = !ad.IsMesoExplosion && pickPocketActivated && pickPocketSkillLevel > 0 && pickPocketSLD != null; int StolenMP = 0; int MpStealSkillID = chr.Skills.GetMpStealSkillData(2, out int MpStealProp, out int MpStealPercent, out byte MpStealLevel); List <Drop> dropsToPop = null; short delayForMesoExplosionKill = 0; if (ad.SkillID == Constants.ChiefBandit.Skills.MesoExplosion) { byte items = packet.ReadByte(); dropsToPop = new List <Drop>(items); byte i; for (i = 0; i < items; i++) { int objectID = packet.ReadInt(); packet.Skip(1); if (chr.Field.DropPool.Drops.TryGetValue(objectID, out var drop) && drop.Reward.Mesos) { dropsToPop.Add(drop); } } delayForMesoExplosionKill = packet.ReadShort(); } var sld = ad.SkillID == 0 ? null : DataProvider.Skills[ad.SkillID].Levels[ad.SkillLevel]; long buffTime = sld?.BuffTime * 1000 ?? 0; long buffExpireTime = MasterThread.CurrentTime + buffTime; bool IsSuccessRoll() => sld != null && (Rand32.Next() % 100) < sld.Property; foreach (var ai in ad.Attacks) { try { TotalDamage = 0; mob = chr.Field.GetMob(ai.MobMapId); if (mob == null) { continue; } bool boss = mob.Data.Boss; if (MpStealPercent > 0) { StolenMP += mob.OnMobMPSteal(MpStealProp, MpStealPercent / ad.Targets); } if (pickOk) { mob.GiveMoney(chr, ai, ad.Hits); } foreach (var amount in ai.Damages) { mob.GiveDamage(chr, amount); TotalDamage += amount; } if (TotalDamage == 0) { continue; } var maxDamage = 5 + (chr.Level * 6); if (ad.SkillID == 0 && chr.Level < 10 && TotalDamage > maxDamage) { chr.PermaBan("Melee damage hack (low level), hit " + TotalDamage + " (max: " + maxDamage + ")"); return; } died = mob.CheckDead(ai.HitPosition, ad.IsMesoExplosion ? delayForMesoExplosionKill : ai.HitDelay, chr.PrimaryStats.BuffMesoUP.N); //TODO sometimes when attacking without using a skill this gets triggered and throws a exception? if (died || ad.SkillID <= 0) { continue; } if (ad.SkillID != 0) { MobStatus.MobStatValue addedStats = 0; switch (ad.SkillID) { case Constants.DragonKnight.Skills.Sacrifice: { double percentSacrificed = sld.XValue / 100.0; short amountSacrificed = (short)(TotalDamage * percentSacrificed); chr.DamageHP(amountSacrificed); break; } case Constants.Bandit.Skills.Steal: if (!boss && IsSuccessRoll()) { mob.GiveReward(chr.ID, 0, DropType.Normal, ai.HitPosition, ai.HitDelay, 0, true); } break; // Debuffs case Constants.Rogue.Skills.Disorder: addedStats = mob.Status.BuffPhysicalDamage.Set(ad.SkillID, (short)sld.XValue, buffExpireTime); addedStats |= mob.Status.BuffPhysicalDefense.Set(ad.SkillID, (short)sld.XValue, buffExpireTime); break; case Constants.WhiteKnight.Skills.ChargeBlow: // Not sure if this should add the stun case Constants.Crusader.Skills.AxeComa: case Constants.Crusader.Skills.SwordComa: case Constants.Crusader.Skills.Shout: if (!boss && IsSuccessRoll()) { addedStats = mob.Status.BuffStun.Set(ad.SkillID, (short)-sld.BuffTime, buffExpireTime); } //is charge blow supposed to end the elemental charge buff? break; case Constants.Crusader.Skills.AxePanic: case Constants.Crusader.Skills.SwordPanic: if (!boss && IsSuccessRoll()) { addedStats = mob.Status.BuffDarkness.Set(ad.SkillID, (short)1, buffExpireTime); //darkness animation doesnt show in this ver? } break; } if (addedStats != 0) { MobPacket.SendMobStatsTempSet(mob, ai.HitDelay, addedStats); } } if (StolenMP > 0) { chr.ModifyMP((short)StolenMP); MapPacket.SendPlayerSkillAnimSelf(chr, MpStealSkillID, MpStealLevel); MapPacket.SendPlayerSkillAnim(chr, MpStealSkillID, MpStealLevel); } if (ad.SkillID != 0) { MaxPossibleDamage = DamageFormula.MaximumMeleeDamage(chr, mob, ad.Targets, ad.SkillID); } else { MaxPossibleDamage = DamageFormula.MaximumMeleeDamage(chr, mob, ad.Targets); } if (TotalDamage > MaxPossibleDamage) { if (ReportDamagehack(chr, ad, TotalDamage, (int)MaxPossibleDamage)) { return; } } } catch (Exception ex) { Program.MainForm.LogAppend(ex.ToString()); } } if (chr.PrimaryStats.BuffComboAttack.IsSet() && TotalDamage > 0) { if (ad.SkillID == Constants.Crusader.Skills.AxeComa || ad.SkillID == Constants.Crusader.Skills.SwordComa || ad.SkillID == Constants.Crusader.Skills.AxePanic || ad.SkillID == Constants.Crusader.Skills.SwordPanic) { chr.PrimaryStats.BuffComboAttack.N = 1; BuffPacket.SetTempStats(chr, BuffValueTypes.ComboAttack); MapPacket.SendPlayerBuffed(chr, BuffValueTypes.ComboAttack); } else if (ad.SkillID != Constants.Crusader.Skills.Shout) { if (chr.PrimaryStats.BuffComboAttack.N <= chr.PrimaryStats.BuffComboAttack.MaxOrbs) { chr.PrimaryStats.BuffComboAttack.N++; BuffPacket.SetTempStats(chr, BuffValueTypes.ComboAttack); MapPacket.SendPlayerBuffed(chr, BuffValueTypes.ComboAttack); } } } switch (ad.SkillID) { case 0: // Normal wep { if (chr.Inventory.GetEquippedItemId((short)Constants.EquipSlots.Slots.Helm, true) == 1002258) // Blue Diamondy Bandana { var mobs = chr.Field.GetMobsInRange(chr.Position, new Pos(-10000, -10000), new Pos(10000, 10000)); foreach (var m in mobs) { MobPacket.SendMobDamageOrHeal(chr.Field, m, 1337, false, false); if (m.GiveDamage(chr, 1337)) { m.CheckDead(); } } } break; } case Constants.ChiefBandit.Skills.MesoExplosion: { byte i = 0; foreach (var drop in dropsToPop) { var delay = (short)Math.Min(1000, delayForMesoExplosionKill + (100 * (i % 5))); chr.Field.DropPool.RemoveDrop(drop, RewardLeaveType.Explode, delay); i++; } break; } case Constants.WhiteKnight.Skills.ChargeBlow: if (IsSuccessRoll()) { // RIP. It cancels your charge var removedBuffs = chr.PrimaryStats.RemoveByReference(chr.PrimaryStats.BuffCharges.R); BuffPacket.SetTempStats(chr, removedBuffs); MapPacket.SendPlayerBuffed(chr, removedBuffs); } break; case Constants.WhiteKnight.Skills.BwFireCharge: case Constants.WhiteKnight.Skills.BwIceCharge: case Constants.WhiteKnight.Skills.BwLitCharge: case Constants.WhiteKnight.Skills.SwordFireCharge: case Constants.WhiteKnight.Skills.SwordIceCharge: case Constants.WhiteKnight.Skills.SwordLitCharge: { var buff = chr.PrimaryStats.BuffCharges.Set( ad.SkillID, sld.XValue, BuffStat.GetTimeForBuff(1000 * sld.BuffTime) ); BuffPacket.SetTempStats(chr, buff); MapPacket.SendPlayerBuffed(chr, buff); break; } case Constants.DragonKnight.Skills.DragonRoar: { // Apply stun var buff = chr.PrimaryStats.BuffStun.Set( ad.SkillID, 1, BuffStat.GetTimeForBuff(1000 * sld.YValue) ); BuffPacket.SetTempStats(chr, buff); MapPacket.SendPlayerBuffed(chr, buff); break; } } }
public static List <Reward> GetRewards(Character Owner, Map Field, int ID, char Type, bool PremiumMap, double Showdown) { double HourDropRateIncrease = 1.0; var curDate = MasterThread.CurrentDate; if (curDate.Hour >= 13 && curDate.Hour < 19) { HourDropRateIncrease = ms_fIncDropRate_WSE; } double dRegionalIncRate = Field.m_dIncRate_Drop; double dwOwnerDropRate = Owner.m_dIncDropRate; double dwOwnerDropRate_Ticket = Owner.m_dIncDropRate_Ticket; var Result = new List <Reward>(); if (!DataProvider.Drops.TryGetValue($"{Type}{ID}", out var Rewards)) { return(Result); } foreach (var Drop in Rewards) { if ((Drop.Premium && !PremiumMap)) { continue; } var itemDropRate = 1.0; if (Drop.Mesos == 0) { itemDropRate = dwOwnerDropRate_Ticket; } var maxDropChance = (long)(1000000000.0 / (ms_fIncDropRate * HourDropRateIncrease) / dRegionalIncRate / Showdown / dwOwnerDropRate / itemDropRate / MonsterCarnivalRewardRate); var luckyNumber = Rand32.Next() % maxDropChance; if (luckyNumber >= Drop.Chance) { continue; } // Don't care about items that are 'expired' if (Drop.Mesos != 0 && Drop.DateExpire <= curDate) { continue; } var Reward = new Reward() { Mesos = Drop.Mesos != 0, Drop = Drop.Mesos != 0 ? Drop.Mesos : Drop.ItemID, Data = Drop.Mesos != 0 ? null : BaseItem.CreateFromItemID(Drop.ItemID, GetItemAmount(Drop.ItemID, Drop.Min, Drop.Max)) }; if (!Reward.Mesos) { Reward.Data.GiveStats(ItemVariation.Normal); if (Drop.Period > 0) { Reward.Data.Expiration = Tools.GetFileTimeWithAddition(new TimeSpan(Drop.Period, 0, 0, 0)); } else if (Drop.DateExpire != DateTime.MaxValue) { Reward.Data.Expiration = Drop.DateExpire.ToFileTimeUtc(); } } if (!Drop.Premium || PremiumMap) { if (Reward.Mesos) { int minDrop = 4 * Reward.Drop / 5; int maxDrop = 2 * Reward.Drop / 5 + 1; int DroppedMesos = (int)(minDrop + Rand32.Next() % maxDrop); if (DroppedMesos <= 1) { DroppedMesos = 1; } DroppedMesos = (int)(DroppedMesos * dwOwnerDropRate_Ticket); Reward.Drop = DroppedMesos; } } Result.Add(Reward); } return(Result); }
public static void HandleMobControl(Character victim, Packet packet) { int mobid = packet.ReadInt(); var mob = victim.Field.GetMob(mobid); short moveID = packet.ReadShort(); var x = packet.ReadByte(); bool bNextAttackPossible = (x & 0x0F) != 0; sbyte action = packet.ReadSByte(); int actualAction = action < 0 ? -1 : (byte)(action >> 1); uint dwData = packet.ReadUInt(); var movePath = new MovePath(); movePath.DecodeFromPacket(packet, MovePath.MovementSource.Mob); if (mob == null) { return; } if (mob.Controller != victim && (!bNextAttackPossible || mob.NextAttackPossible || !mob.Field.FindNewController(mob, victim, true))) { SendMobRequestEndControl(victim, mobid); return; } victim.TryTraceMovement(movePath); if (mob.Controller != null && victim.ID != mob.Controller.ID) { Program.MainForm.LogAppend("returning mobpacket"); return; } var lastMoveMillis = MasterThread.CurrentTime - mob.LastMove; bool justStartedControlling = (MasterThread.CurrentTime - mob.LastControllerAssignTime) < 2000; PacketHelper.ValidateMovePath(mob, movePath); //Program.MainForm.LogDebug("[" + DateTime.Now.ToString() + "]" + "Received movement packet from " + victim.Name + ". Original pos: x:" + movePath.OriginalPosition.X + " y: " + movePath.OriginalPosition.Y + "\r\nNew position: x: " + movePath.NewPosition.X + " y: " + movePath.NewPosition.Y); // Skill related? if (actualAction >= 21 && actualAction <= 25) { short attackDelay = (short)(dwData >> 16); byte level = (byte)(dwData >> 8); byte skillId = (byte)(dwData); if (mob.DoSkill(skillId, level, attackDelay) == false) { // invalid return; } } else if (actualAction > 12 && actualAction < 20) { // regular attack? var attackIdx = (byte)(actualAction - 12); if (mob.Data.Attacks == null || !mob.Data.Attacks.ContainsKey(attackIdx)) { Program.MainForm.LogAppend( "Unknown attack for mob: " + mob.MobID + "; " + attackIdx + "; " + packet); } else { var attack = mob.Data.Attacks[attackIdx]; mob.MP = Math.Max(0, mob.MP - attack.MPConsume); mob.LastAttack = MasterThread.CurrentTime; } } if ((MasterThread.CurrentTime - mob.LastAttack) > 5000) { // Reassign controller! MobDamageInfo lastHitCharacter; if (mob.IsControlled) { var currentControllerID = mob.Controller.ID; lastHitCharacter = mob.DamageLog.Log.FirstOrDefault(mobDamageInfo => { if (mobDamageInfo.CharacterID == currentControllerID) { return(false); } if (mob.Field.GetPlayer(mobDamageInfo.CharacterID) == null) { return(false); } return((MasterThread.CurrentDate - mobDamageInfo.Time).TotalSeconds <= 5.0); }); } else { lastHitCharacter = mob.DamageLog.Log.FirstOrDefault(mobDamageInfo => { if (mob.Field.GetPlayer(mobDamageInfo.CharacterID) == null) { return(false); } return((MasterThread.CurrentDate - mobDamageInfo.Time).TotalSeconds <= 5.0); }); } if (lastHitCharacter != null) { Program.MainForm.LogDebug("Setting new controller: " + Server.Instance.GetCharacter(lastHitCharacter.CharacterID)); mob.SetController(mob.Field.GetPlayer(lastHitCharacter.CharacterID), true, false); return; } } mob.NextAttackPossible = bNextAttackPossible; // Prepare next skill byte forceControllerSkillLevel = 0; if (mob.NextAttackPossible == false || mob.SkillCommand != 0 || (mob.HasAnyStatus && mob.Status.BuffSealSkill.IsSet()) || mob.Data.Skills == null || mob.Data.Skills.Count == 0 || (MasterThread.CurrentTime - mob.LastSkillUse) < 3000) { // No skill } else { var availableSkills = mob.Data.Skills.Where(skill => { Dictionary <byte, MobSkillLevelData> msdLevels; if (!DataProvider.MobSkills.TryGetValue(skill.SkillID, out msdLevels) || !msdLevels.ContainsKey(skill.Level)) { return(false); } // Handle HP restriction var msd = msdLevels[skill.Level]; if (msd.HPLimit > 0 && (mob.HP / (double)mob.MaxHP * 100.0) > msd.HPLimit) { return(false); } // Skip if we already used a skill and it was not yet cooled down if (mob.SkillsInUse.TryGetValue(msd.SkillID, out long lastUse) && (lastUse + (msd.Cooldown * 1000)) > MasterThread.CurrentTime) { return(false); } // Do not reach the summon limit if (skill.SkillID == (byte)Constants.MobSkills.Skills.Summon && mob.SummonCount + msd.Summons.Count > msd.SummonLimit) { return(false); } // Can we boost stats? if (mob.HasAnyStatus) { short currentX = 0; int maxX = Math.Abs(100 - msd.X); switch ((Constants.MobSkills.Skills)skill.SkillID) { case Constants.MobSkills.Skills.WeaponAttackUp: case Constants.MobSkills.Skills.WeaponAttackUpAoe: currentX = mob.Status.BuffPowerUp.N; break; case Constants.MobSkills.Skills.MagicAttackUp: case Constants.MobSkills.Skills.MagicAttackUpAoe: currentX = mob.Status.BuffMagicUp.N; break; case Constants.MobSkills.Skills.WeaponDefenseUp: case Constants.MobSkills.Skills.WeaponDefenseUpAoe: currentX = mob.Status.BuffPowerGuardUp.N; break; case Constants.MobSkills.Skills.MagicDefenseUp: case Constants.MobSkills.Skills.MagicDefenseUpAoe: currentX = mob.Status.BuffMagicGuardUp.N; break; } if (currentX == 0) { return(true); } if (Math.Abs(100 - currentX) >= maxX) { return(false); } } return(true); }).ToArray(); if (availableSkills.Length > 0) { var randomSkill = availableSkills[Rand32.Next() % availableSkills.Length]; mob.SkillCommand = randomSkill.SkillID; forceControllerSkillLevel = randomSkill.Level; } } byte forceControllerSkillID = mob.SkillCommand; // Fix crash (zero level skill) if (forceControllerSkillLevel == 0) { forceControllerSkillID = 0; } SendMobControlResponse(victim, mobid, moveID, bNextAttackPossible, (short)mob.MP, forceControllerSkillID, forceControllerSkillLevel); SendMobControlMove(victim, mob, bNextAttackPossible, (byte)action, dwData, movePath); mob.CheckVacHack(lastMoveMillis, movePath.OriginalPosition, movePath.NewPosition, victim); // Good luck on getting less. if (lastMoveMillis < 500 && !justStartedControlling && !victim.IsAFK) { if (victim.AssertForHack(mob.HackReportCounter++ > 5, $"Movement speed too high! {lastMoveMillis}ms since last movement.")) { mob.HackReportCounter = 0; } } }
public static void HandleUseSkill(Character chr, Packet packet) { if (chr.PrimaryStats.HP == 0) { // We don't like zombies InventoryPacket.NoChange(chr); return; } var field = chr.Field; var SkillID = packet.ReadInt(); var SkillLevel = packet.ReadByte(); if (SkillID == Constants.Priest.Skills.MysticDoor && MasterThread.CurrentTime - chr.tLastDoor < 3000) { //hack fix for door dc bug InventoryPacket.NoChange(chr); return; } if (!chr.Skills.Skills.ContainsKey(SkillID) || SkillLevel < 1 || SkillLevel > chr.Skills.Skills[SkillID]) { Program.MainForm.LogAppend("Player {0} tried to use a skill without having it.", chr.ID); ReportManager.FileNewReport("Player {0} tried to use a skill without having it.", chr.ID, 0); chr.Player.Socket.Disconnect(); return; } var isGMHideSkill = SkillID == Constants.Gm.Skills.Hide; // Prevent sending the hide enable/disable packet to others if (isGMHideSkill) { chr.SetHide(chr.GMHideEnabled == false, false); if (chr.GMHideEnabled == false) { StopSkill(chr, SkillID); } } else if (SkillID != Constants.Cleric.Skills.Heal || chr.Inventory.GetEquippedItemId((short)Constants.EquipSlots.Slots.Weapon, false) == 0) { // If you are using Heal, and are not using a wand/weapon, it won't show anything. MapPacket.SendPlayerSkillAnim(chr, SkillID, SkillLevel); } var sld = DataProvider.Skills[SkillID].Levels[SkillLevel]; if (SkillID == (int)Constants.Spearman.Skills.HyperBody && !chr.PrimaryStats.HasBuff((int)Constants.Spearman.Skills.HyperBody)) // Buff already exists, do not execute bonus again. Allow multiple casts for duration refresh { var hpmpBonus = (short)((double)chr.PrimaryStats.MaxHP * ((double)sld.XValue / 100.0d)); chr.PrimaryStats.BuffBonuses.MaxHP = hpmpBonus; hpmpBonus = (short)((double)chr.PrimaryStats.MaxMP * ((double)sld.YValue / 100.0d)); chr.PrimaryStats.BuffBonuses.MaxMP = hpmpBonus; } short skillDelay = 0; IEnumerable <Character> getCharactersForPartyBuff(byte Flags, bool deadPlayersToo = false) { if (chr.PartyID == 0) { yield break; } if (PartyData.Parties.TryGetValue(chr.PartyID, out var party)) { for (var i = 5; i >= 0; i--) { if ((Flags >> (5 - i) & 1) == 0) { continue; } var charid = party.Members[i]; if (charid == 0) { continue; } var affected = Server.Instance.GetCharacter(charid); if (affected != null && chr.MapID == affected.MapID && (deadPlayersToo || affected.PrimaryStats.HP > 0)) { yield return(affected); } } } } void handlePartyEffects(byte Flags, short Delay, bool deadPlayersToo = false, Action <Character> additionalEffects = null) { handlePartyEffectsWithPlayers(getCharactersForPartyBuff(Flags, deadPlayersToo), Delay, additionalEffects); } void handlePartyEffectsWithPlayers(IEnumerable <Character> characters, short Delay, Action <Character> additionalEffects = null) { foreach (var character in characters) { if (character == chr) { continue; } if (!computerSaysYes()) { continue; } MapPacket.SendPlayerSkillAnimThirdParty(character, SkillID, SkillLevel, true, true); MapPacket.SendPlayerSkillAnimThirdParty(character, SkillID, SkillLevel, true, false); additionalEffects?.Invoke(character); } } // Runs func() for each mob inside the packet that: // - is not a boss (when isBossable is false) // - effect chance was a success void handleMobStatEffects(bool isBossable, Action <Mob, short> func) { var mobCount = packet.ReadByte(); var mobs = new List <Mob>(mobCount); for (var i = 0; i < mobCount; i++) { var mobId = packet.ReadInt(); var mob = field.GetMob(mobId); if (mob == null) { continue; } if (chr.AssertForHack(mob.IsBoss && !isBossable, "Tried hitting boss with non-boss skill", false)) { continue; } if (computerSaysYes()) { mobs.Add(mob); } } var delay = packet.ReadShort(); mobs.ForEach(x => func(x, delay)); } IEnumerable <Character> getFullMapPlayersForGMSkill() { return(field.Characters.Where(victim => { if (victim == chr) { return false; } if (chr.GMHideEnabled && victim.GMHideEnabled == false) { return false; } // Only Admins can buff other regular people if (chr.IsGM && !chr.IsAdmin && !victim.IsGM) { return false; } return true; })); } bool computerSaysYes() { var chance = sld.Property; if (chance == 0) { chance = 100; } return(!(Rand32.Next() % 100 >= chance)); } //TODO refactor copy-pasted "nearest 6 mobs" logic switch (SkillID) { case Constants.Assassin.Skills.Haste: case Constants.Bandit.Skills.Haste: case Constants.Cleric.Skills.Bless: case Constants.Spearman.Skills.IronWill: case Constants.Fighter.Skills.Rage: case Constants.FPWizard.Skills.Meditation: case Constants.ILWizard.Skills.Meditation: case Constants.Hermit.Skills.MesoUp: { var Flags = packet.ReadByte(); var Delay = packet.ReadShort(); handlePartyEffects(Flags, Delay, false, victim => { victim.Buffs.AddBuff(SkillID, SkillLevel); }); break; } case Constants.Spearman.Skills.HyperBody: { var Flags = packet.ReadByte(); var Delay = packet.ReadShort(); handlePartyEffects(Flags, Delay, false, victim => { if (!victim.PrimaryStats.HasBuff((int)Constants.Spearman.Skills.HyperBody)) // Buff already exists, do not execute bonus again. Allow multiple casts for duration refresh { var hpmpBonus = (short)((double)victim.PrimaryStats.MaxHP * ((double)sld.XValue / 100.0d)); victim.PrimaryStats.BuffBonuses.MaxHP = hpmpBonus; hpmpBonus = (short)((double)victim.PrimaryStats.MaxMP * ((double)sld.YValue / 100.0d)); victim.PrimaryStats.BuffBonuses.MaxMP = hpmpBonus; } victim.Buffs.AddBuff(SkillID, SkillLevel); }); break; } case Constants.Cleric.Skills.Heal: { var Flags = packet.ReadByte(); var Delay = packet.ReadShort(); var members = getCharactersForPartyBuff(Flags, false); var count = members.Count(); double healRate = 0; if (sld.HPProperty > 0) { int chrInt = chr.PrimaryStats.getTotalInt(); int chrLuk = chr.PrimaryStats.getTotalLuk(); var rate = (chrInt * 0.8) + Rand32.Next() % (chrInt * 0.2); healRate = (((rate * 1.5 + chrLuk) * (chr.PrimaryStats.getTotalMagicAttack() + chr.PrimaryStats.BuffMagicAttack.N) * 0.01) * (count * 0.3 + 1.0) * sld.HPProperty * 0.01); } var bigHeal = Math.Min(((long)healRate / (count == 0 ? 1 : count)), short.MaxValue); //prevent integer overflow caused by high stats. Set count to 1 when not in party var heal = (short)bigHeal; chr.ModifyHP((short)Math.Min(heal, (chr.PrimaryStats.GetMaxHP() - chr.PrimaryStats.HP))); handlePartyEffectsWithPlayers(members, Delay, victim => { int oldHP = victim.PrimaryStats.HP; victim.ModifyHP((short)Math.Min(heal, (victim.PrimaryStats.GetMaxHP() - victim.PrimaryStats.HP))); chr.AddEXP(20 * ((victim.PrimaryStats.HP - oldHP) / (8 * victim.Level + 190)), true); }); break; } case Constants.Priest.Skills.Dispel: { var Flags = packet.ReadByte(); var Delay = packet.ReadShort(); if (computerSaysYes()) { chr.Buffs.Dispell(); } handlePartyEffects(Flags, Delay, false, victim => { victim.Buffs.Dispell(); }); handleMobStatEffects(false, (mob, delay) => { MobStatus.MobStatValue Flag = 0; Flag |= mob.Status.BuffPowerUp.Reset(); Flag |= mob.Status.BuffMagicUp.Reset(); Flag |= mob.Status.BuffMagicGuardUp.Reset(); Flag |= mob.Status.BuffPowerGuardUp.Reset(); Flag |= mob.Status.BuffHardSkin.Reset(); MobPacket.SendMobStatsTempReset(mob, Flag); }); break; } case Constants.Priest.Skills.HolySymbol: { var Flags = packet.ReadByte(); var Delay = packet.ReadShort(); handlePartyEffects(Flags, Delay, false, victim => { victim.Buffs.AddBuff(SkillID, SkillLevel); }); break; } // DOOR case Constants.Priest.Skills.MysticDoor: { var x = packet.ReadShort(); var y = packet.ReadShort(); if (chr.DoorMapId != Constants.InvalidMap) { DataProvider.Maps[chr.DoorMapId].DoorPool.TryRemoveDoor(chr.ID); } chr.DoorMapId = chr.MapID; field.DoorPool.CreateDoor(chr, x, y, MasterThread.CurrentTime + sld.BuffTime * 1000); chr.tLastDoor = MasterThread.CurrentTime; break; } // GM SKILLS case Constants.Gm.Skills.Haste: case Constants.Gm.Skills.HolySymbol: case Constants.Gm.Skills.Bless: { getFullMapPlayersForGMSkill().ForEach(victim => { MapPacket.SendPlayerSkillAnimThirdParty(victim, SkillID, SkillLevel, true, true); MapPacket.SendPlayerSkillAnimThirdParty(victim, SkillID, SkillLevel, true, false); victim.Buffs.AddBuff(SkillID, SkillLevel); }); break; } case Constants.Gm.Skills.HealPlusDispell: { getFullMapPlayersForGMSkill().ForEach(victim => { MapPacket.SendPlayerSkillAnimThirdParty(victim, SkillID, SkillLevel, true, true); MapPacket.SendPlayerSkillAnimThirdParty(victim, SkillID, SkillLevel, true, false); victim.ModifyHP(victim.PrimaryStats.GetMaxMP(false), true); victim.ModifyMP(victim.PrimaryStats.GetMaxMP(false), true); victim.Buffs.Dispell(); }); chr.ModifyHP(chr.PrimaryStats.GetMaxMP(false), true); chr.ModifyMP(chr.PrimaryStats.GetMaxMP(false), true); chr.Buffs.Dispell(); break; } case Constants.Gm.Skills.Resurrection: { getFullMapPlayersForGMSkill().ForEach(victim => { if (victim.PrimaryStats.HP <= 0) { MapPacket.SendPlayerSkillAnimThirdParty(victim, SkillID, SkillLevel, true, true); MapPacket.SendPlayerSkillAnimThirdParty(victim, SkillID, SkillLevel, true, false); victim.ModifyHP(victim.PrimaryStats.GetMaxHP(false), true); } }); break; } // MOB SKILLS case Constants.Page.Skills.Threaten: { var buffTime = MasterThread.CurrentTime + (sld.BuffTime * 1000); handleMobStatEffects(false, (mob, delay) => { var stat = mob.Status.BuffPhysicalDamage.Set( SkillID, (short)-((mob.Data.PAD * SkillLevel) / 100), buffTime + delay ); stat |= mob.Status.BuffPhysicalDefense.Set( SkillID, (short)-((mob.Data.PDD * SkillLevel) / 100), buffTime + delay ); MobPacket.SendMobStatsTempSet(mob, delay, stat); }); break; } case Constants.FPWizard.Skills.Slow: case Constants.ILWizard.Skills.Slow: { var buffNValue = sld.XValue; var buffTime = MasterThread.CurrentTime + (sld.BuffTime * 1000); handleMobStatEffects(false, (mob, delay) => { MobPacket.SendMobStatsTempSet(mob, delay, mob.Status.BuffSpeed.Set(SkillID, buffNValue, buffTime + delay)); }); break; } case Constants.Gm.Skills.ItemExplosion: { field.DropPool.Clear(RewardLeaveType.Explode); // TODO: Explode people and such break; } case Constants.WhiteKnight.Skills.MagicCrash: { handleMobStatEffects(false, (mob, delay) => { MobPacket.SendMobStatsTempReset(mob, mob.Status.BuffMagicGuardUp.Reset()); }); break; } case Constants.DragonKnight.Skills.PowerCrash: { handleMobStatEffects(false, (mob, delay) => { MobPacket.SendMobStatsTempReset(mob, mob.Status.BuffPowerUp.Reset()); }); break; } case Constants.Crusader.Skills.ArmorCrash: { handleMobStatEffects(false, (mob, delay) => { MobPacket.SendMobStatsTempReset(mob, mob.Status.BuffPowerGuardUp.Reset()); }); break; } case Constants.ILMage.Skills.Seal: case Constants.FPMage.Skills.Seal: { var buffTime = MasterThread.CurrentTime + (sld.BuffTime * 1000); handleMobStatEffects(false, (mob, delay) => { MobPacket.SendMobStatsTempSet(mob, delay, mob.Status.BuffSeal.Set(SkillID, 1, buffTime + delay)); }); break; } case Constants.Hermit.Skills.ShadowWeb: { var buffTime = MasterThread.CurrentTime + (sld.BuffTime * 1000); handleMobStatEffects(false, (mob, delay) => { var stat = mob.Status.BuffWeb.Set( SkillID, (short)(mob.MaxHP / (50 - SkillLevel)), buffTime + delay ); MobPacket.SendMobStatsTempSet(mob, delay, stat); }); break; } case Constants.Priest.Skills.Doom: { var buffTime = MasterThread.CurrentTime + (sld.BuffTime * 1000); handleMobStatEffects(false, (mob, delay) => { MobPacket.SendMobStatsTempSet(mob, delay, mob.Status.BuffDoom.Set(SkillID, 1, buffTime + delay)); }); break; } // SUMMONS case Constants.Priest.Skills.SummonDragon: case Constants.Ranger.Skills.SilverHawk: case Constants.Sniper.Skills.GoldenEagle: { var X = packet.ReadShort(); var Y = packet.ReadShort(); var fh = field.GetFootholdUnderneath(X, Y, out var MaxY); ushort fhid = 0; fhid = fh?.ID ?? (ushort)chr.Foothold; var bird = new Summon(chr, SkillID, SkillLevel, X, Y, true, fhid, MasterThread.CurrentTime + sld.BuffTime * 1000); chr.Summons.SetSummon(bird); break; } case Constants.Ranger.Skills.Puppet: case Constants.Sniper.Skills.Puppet: { Program.MainForm.LogDebug(packet.ToString()); var X = packet.ReadShort(); var Y = packet.ReadShort(); var fh = field.GetFootholdUnderneath(X, Y, out var MaxY); var floor = field.FindFloor(new Pos(X, Y)); ushort fhid = 0; fhid = fh?.ID ?? (ushort)chr.Foothold; var puppet = new Puppet(chr, SkillID, SkillLevel, floor.X, floor.Y, false, fhid, MasterThread.CurrentTime + sld.BuffTime * 1000, sld.XValue); chr.Summons.SetSummon(puppet); break; } } if (packet.Length == packet.Position + 2) { // Read the delay... skillDelay = packet.ReadShort(); } // Handle regular skill stuff if (!isGMHideSkill || chr.GMHideEnabled) { chr.Buffs.AddBuff(SkillID, SkillLevel, skillDelay); } InventoryPacket.NoChange(chr); chr.Skills.DoSkillCost(SkillID, SkillLevel); if (sld.Speed > 0) { MapPacket.SendAvatarModified(chr, MapPacket.AvatarModFlag.Speed); } }
public static void HandleScrollItem(Character chr, Packet packet) { short scrollslot = packet.ReadShort(); short itemslot = packet.ReadShort(); if (itemslot < -100) { _scrollingLog.Warn($"Tried to scroll a cashequip on slot {scrollslot}"); NoChange(chr); return; } BaseItem scroll = chr.Inventory.GetItem(2, scrollslot); EquipItem equip = (EquipItem)chr.Inventory.GetItem(1, itemslot); if (scroll == null || equip == null || Constants.itemTypeToScrollType(equip.ItemID) != Constants.getScrollType(scroll.ItemID) || !DataProvider.Items.TryGetValue(scroll.ItemID, out ItemData scrollData) ) { _scrollingLog.Warn($"Tried to use a scroll that didn't exist {scroll == null}, equip that didnt exist {equip == null}, scroll types that didnt match or no scroll data available."); NoChange(chr); return; } if (scrollData.ScrollSuccessRate == 0 || equip.Slots == 0) { _scrollingLog.Warn($"Tried to scroll, but the equip has no scrolls left {equip.Slots} or zero success rate {scrollData.ScrollSuccessRate}"); NoChange(chr); return; } chr.Inventory.TakeItem(scroll.ItemID, 1); var chanceRoll = Rand32.Next() % 100; bool succeeded = false; bool cursed = false; if (chanceRoll < scrollData.ScrollSuccessRate) { equip.Str += scrollData.IncStr; equip.Dex += scrollData.IncDex; equip.Int += scrollData.IncInt; equip.Luk += scrollData.IncLuk; equip.HP += scrollData.IncMHP; equip.MP += scrollData.IncMMP; equip.Watk += scrollData.IncWAtk; equip.Wdef += scrollData.IncWDef; equip.Matk += scrollData.IncMAtk; equip.Mdef += scrollData.IncMDef; equip.Acc += scrollData.IncAcc; equip.Avo += scrollData.IncAvo; equip.Jump += scrollData.IncJump; equip.Speed += scrollData.IncSpeed; equip.Scrolls++; equip.Slots--; succeeded = true; AddItem(chr, 1, equip, true); MapPacket.SendAvatarModified(chr, MapPacket.AvatarModFlag.Equips); SendItemScrolled(chr, true); } else { if (chanceRoll < scrollData.ScrollCurseRate) { cursed = true; chr.Inventory.TryRemoveCashItem(equip); SwitchSlots(chr, itemslot, 0, 1); chr.Inventory.SetItem(1, itemslot, null); SendItemScrolled(chr, false); chr.PrimaryStats.CheckBoosters(); } else { equip.Slots--; AddItem(chr, 1, equip, true); SendItemScrolled(chr, false); } } _scrollingLog.Info(new ScrollResult { itemData = equip, itemId = equip.ItemID, scrollData = scrollData, scrollId = scroll.ItemID, succeeded = succeeded, cursed = cursed }); }