/// <summary> /// Recoding of the WearObject function into a table-based system which is fairly /// complex, but easy to maintain via the table. Bitshifting is used to check /// each wear location for a piece of equipment. /// /// (not enabled) /// /// TODO: Enable/finish this or remove it. See WearObject for what it is intended /// to replace. /// </summary> /// <param name="ch"></param> /// <param name="obj"></param> /// <param name="replaceExisting"></param> static void NewWearObject(CharData ch, ref Object obj, bool replaceExisting) { int count; for (count = 0; count < WearData.Table.Length; ++count) { if (obj.HasWearFlag(new Bitvector(0, (1 << count)))) { if (WearData.Table[count].BodyPartNeeded == 0 || Macros.IsSet((int)Race.RaceList[ch.GetRace()].BodyParts, WearData.Table[count].BodyPartNeeded)) { if (WearData.Table[count].RacesNotAllowed == -1 || ch.GetRace() != WearData.Table[(1 << count)].RacesNotAllowed) { bool found = false; int wearLoc = 0; foreach (int wear in WearData.Table[count].WearLocations) { if (wear != 0 && !ch.RemoveObject((ObjTemplate.WearLocation)wear, replaceExisting)) { continue; } wearLoc = wear; found = true; break; } if (!found) { return; } SocketConnection.Act(WearData.Table[(1 << count)].WearMessageToWearer, ch, obj, null, SocketConnection.MessageTarget.character); SocketConnection.Act(WearData.Table[(1 << count)].WearMessageToRoom, ch, obj, null, SocketConnection.MessageTarget.room); // Need to allow for multiple wear locations rather than just picking the first one. ch.EquipObject(ref obj, (ObjTemplate.WearLocation)wearLoc); return; } ch.SendText("Your race cannot wear that type of equipment.\r\n"); return; } ch.SendText("The design of your body prevents you from using that.\r\n"); return; } } }
/// <summary> /// Springleap. Can be used to initiate combat and can be used during combat. /// </summary> /// <param name="ch"></param> /// <param name="str"></param> public static void Springleap(CharData ch, string[] str) { if( ch == null ) return; int chance; /* Check player's level and class, mobs can use this skill */ if ((!ch.HasSkill("springleap"))) { ch.SendText("You'd better leave the martial arts to Bruce Lee.\r\n"); return; } if (ch.GetRace() == Race.RACE_CENTAUR) { ch.SendText("Your anatomy prevents you from springleaping.\r\n"); return; } if (ch.IsBlind()) { return; } CharData victim = ch.Fighting; if (str.Length != 0) { if (!(victim = ch.GetCharRoom(str[0])) || victim.CurrentPosition == Position.dead) { ch.SendText("You don't see them here.\r\n"); return; } } else { if (!victim || victim.CurrentPosition == Position.dead) { ch.SendText("You aren't fighting anyone.\r\n"); return; } } /* springleap self */ if (ch == victim) { ch.SendText("You can't quite figure out how to do that.\r\n"); return; } /* Check size of ch vs. victim. */ /* If ch is too small. */ if (ch.CurrentSize - 2 > victim.CurrentSize) { SocketConnection.Act("Your acrobatic maneuver cannot accurately leap into such a small being.", ch, null, victim, SocketConnection.MessageTarget.character); return; } /* Ch 2 or more sizes larger than victim => bad! */ if (ch.CurrentSize + 2 < victim.CurrentSize) { SocketConnection.Act("Your acrobatic maneuver does not seem to work on someone so large.", ch, null, victim, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n jumps into you, and slides down your leg.", ch, null, victim, SocketConnection.MessageTarget.victim); SocketConnection.Act("$n&n jumps into $N&n and slides down $S leg.", ch, null, victim, SocketConnection.MessageTarget.room_vict); ch.WaitState(Skill.SkillList["springleap"].Delay); ch.CurrentPosition = Position.reclining; if (victim.Fighting == null) { Combat.SetFighting(victim, ch); } return; } ch.WaitState(MUDMath.FuzzyNumber(Skill.SkillList["springleap"].Delay)); ch.PracticeSkill("springleap"); if (ch.IsNPC()) { chance = (ch.Level * 3) / 2 + 15; } else { chance = ((PC)ch).SkillAptitude["springleap"] - 5; } if (chance > 95) { chance = 95; } if (victim.CurrentPosition < Position.fighting) { chance /= 4; } if (MUDMath.NumberPercent() < chance) { ch.CurrentPosition = Position.fighting; SocketConnection.Act("&+WYour springleap knocks $N&n&+W on $S butt.&n", ch, null, victim, SocketConnection.MessageTarget.character); SocketConnection.Act("&+W$n&n&+W leaps gracefully at $N&n&+W, sending $M to the ground.&n", ch, null, victim, SocketConnection.MessageTarget.room); SocketConnection.Act("&+W$n&n&+W leaps at you, knocking you to the ground!&n", ch, null, victim, SocketConnection.MessageTarget.victim); if (victim.IsAffected(Affect.AFFECT_SINGING)) { victim.RemoveAffect(Affect.AFFECT_SINGING); SocketConnection.Act("$n&n gasps and falls silent as $e falls over backward!", victim, null, null, SocketConnection.MessageTarget.room); victim.SendText("You abort your singing!\r\n"); } if (victim.IsAffected(Affect.AFFECT_CASTING)) { victim.RemoveAffect(Affect.AFFECT_CASTING); SocketConnection.Act("$n&n no longer has any idea what $e was casting.", victim, null, null, SocketConnection.MessageTarget.room); victim.SendText("Lying on the ground, you realize you have no idea what you were just casting.\r\n"); } if (!ch.Fighting) { Combat.SetFighting(ch, victim); } if (!victim.Fighting) { Combat.SetFighting(victim, ch); } if (!Combat.CheckTumble(victim)) { victim.WaitState((Skill.SkillList["springleap"].Delay * 5 / 6)); if (victim.CurrentPosition > Position.sitting) { victim.CurrentPosition = Position.sitting; } Combat.InflictDamage(ch, victim, MUDMath.NumberRange(1, ch.Level), "springleap", ObjTemplate.WearLocation.none, AttackType.DamageType.bludgeon); } else { Combat.InflictDamage(ch, victim, MUDMath.NumberRange(1, (ch.Level / 3)), "springleap", ObjTemplate.WearLocation.none, AttackType.DamageType.bludgeon); ch.SendText("You roll with the blow, finally landing on your feet.\r\n"); SocketConnection.Act("$n&n rolls with the blow, finally landing on $s feet.", victim, null, null, SocketConnection.MessageTarget.room); } } else { bool pissedOff = false; if (ch.Fighting == victim || victim.GetCurrInt() > MUDMath.NumberPercent()) { pissedOff = true; } if (pissedOff) { SocketConnection.Act("As $N&n avoids your leap you crash to the ground.", ch, null, victim, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n crashes to the ground as you avoid $s springleap.", ch, null, victim, SocketConnection.MessageTarget.victim); if (!ch.Fighting) { Combat.SetFighting(ch, victim); } if (!victim.Fighting) { Combat.SetFighting(victim, ch); } } else { SocketConnection.Act("You ungracefully leap at $N and miss, landing head first!", ch, null, victim, SocketConnection.MessageTarget.character); } SocketConnection.Act("$n&n misses a springleap and falls awkwardly to the ground.", ch, null, null, SocketConnection.MessageTarget.room); ch.CurrentPosition = Position.reclining; Combat.InflictDamage(ch, ch, MUDMath.NumberRange(1, 4), "springleap", ObjTemplate.WearLocation.none, AttackType.DamageType.bludgeon); } return; }
/// <summary> /// Shows a character's attribute screen. /// </summary> /// <param name="ch"></param> /// <param name="str"></param> public static void Attributes(CharData ch, string[] str) { if( ch == null ) return; string buf1 = String.Empty; if (ch.IsNPC()) { ch.SendText("&nYour attributes are as would be expected for an NPC.\r\n"); return; } if (ch.IsImmortal() && str.Length != 0) { CharData worldChar = ch.GetCharWorld(str[0]); if (!worldChar) { ch.SendText("No such person.\r\n"); return; } if (worldChar.IsNPC()) { ch.SendText("NPCs don't have skills!\r\n"); return; } } string buf = String.Format( "&+WName: &+G{0}&n &+WLevel: {1}&n\r\n", MUDString.PadStr(ch.Name, 17), ch.Level); buf1 += buf; buf = String.Format( "&+WRace:&n {0} &+WClass:&n {1} &n&+WSex:&n {2}\r\n", MUDString.PadStr(Race.RaceList[ch.GetRace()].ColorName, 16), MUDString.PadStr(ch.CharacterClass.WholistName, 16), System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(ch.GetSexString())); buf1 += buf; // Break a player's size into strings when we get around to it // -- Xangis if (!ch.IsNPC()) { buf = String.Format( "&+WHeight:&n {0} inches &+WWeight:&n {1} pounds &+WSize:&n {2}\r\n", MUDString.PadInt(((PC)ch).Height, 3), MUDString.PadInt(((PC)ch).Weight, 5), Race.SizeString(ch.CurrentSize)); } else { buf = String.Format("&+WSize:&n {0}\r\n", ch.CurrentSize); } buf1 += buf; TimeSpan time = TimeSpan.FromTicks(ch.TimePlayed.Ticks) + (DateTime.Now - ch.LogonTime); int days = (int)time.TotalHours / 24; time = (time - TimeSpan.FromDays(days)); int hours = (int)time.TotalHours; time = (time - TimeSpan.FromHours(hours)); int minutes = (int)time.TotalMinutes; // Age is a hack until we get it coded - Xangis buf = String.Format( "\r\n&+BAge:&n {0} years. &+BPlaying Time:&n {1} days {2} hours {3} minutes.\r\n", MUDString.PadInt(ch.GetAge(), 3), days, hours, minutes); buf1 += buf; // Need to create a function to display character status strings buf = String.Format("&+BStatus:&n {0}", System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(Position.PositionString(ch.CurrentPosition))); if (!ch.IsNPC() && ch.IsAffected(Affect.AFFECT_BERZERK)) { buf += ", &+Rberzerk&n"; } if (!ch.IsNPC() && ch.HasActionBit(PC.PLAYER_MEMORIZING)) { buf += ", Memorizing"; } if (ch.IsAffected(Affect.AFFECT_CASTING)) { buf += ", Casting"; } if (ch.IsAffected(Affect.AFFECT_SINGING)) { buf += ", Singing"; } if (!ch.IsNPC() && ch.HasActionBit(PC.PLAYER_MEDITATING)) { buf += ", Meditating"; } if (!ch.IsNPC() && ch.HasActionBit(PC.PLAYER_CAMPING)) { /* This is ugly and should be moved to its own function */ buf += ", Camping"; } buf += ".\r\n\r\n"; buf1 += buf; // We want players to see the same stats for levels 1-50. // Should create string converters so that we have no decimals displayed // below this point. buf = String.Format(" &+cSTR:&n {0} &+cArmor Class:&n {1}.\r\n", MUDString.PadStr(StringConversion.AbilityScoreString(ch.GetCurrStr()), 15), ch.GetAC()); buf1 += buf; buf = String.Format(" &+cAGI:&n {0} &+cHitroll:&n {1}\r\n", MUDString.PadStr(StringConversion.AbilityScoreString(ch.GetCurrAgi()), 15), StringConversion.BonusString(ch.GetHitroll(ObjTemplate.WearLocation.hand_one))); buf1 += buf; buf = String.Format(" &+cDEX:&n {0} &+cDamroll:&n {1}\r\n", MUDString.PadStr(StringConversion.AbilityScoreString(ch.GetCurrDex()), 15), StringConversion.BonusString(ch.GetDamroll(ObjTemplate.WearLocation.hand_one))); buf1 += buf; buf = String.Format(" &+cCON:&n {0} &+cAlignment:&n {1}\r\n", MUDString.PadStr(StringConversion.AbilityScoreString(ch.GetCurrCon()), 15), StringConversion.AlignmentString(ch)); buf1 += buf; buf = String.Format(" &n&+cINT:&n {0}\r\n", MUDString.PadStr(StringConversion.AbilityScoreString(ch.GetCurrInt()), 15)); buf1 += buf; buf = String.Format(" &+cWIS:&n {0} &+BSaving Throws&n\r\n", MUDString.PadStr(StringConversion.AbilityScoreString(ch.GetCurrWis()), 15)); buf1 += buf; buf = String.Format(" &+cPOW:&n {0} &+cParalysis:&n {1}\r\n", MUDString.PadStr(StringConversion.AbilityScoreString(ch.GetCurrPow()), 15), StringConversion.BonusString(-ch.SavingThrows[0])); buf1 += buf; buf = String.Format(" &+cCHA:&n {0} &+cRod:&n {1}\r\n", MUDString.PadStr(StringConversion.AbilityScoreString(ch.GetCurrCha()), 15), StringConversion.BonusString(-ch.SavingThrows[1])); buf1 += buf; buf = String.Format(" &+cLUK:&n {0} &+cPetrify:&n {1}\r\n", MUDString.PadStr(StringConversion.AbilityScoreString(ch.GetCurrLuck()), 15), StringConversion.BonusString(-ch.SavingThrows[2])); buf1 += buf; buf = String.Format(" &+cBreath:&n {0}\r\n", StringConversion.BonusString(-ch.SavingThrows[3])); buf1 += buf; buf = String.Format("&+BWimpy: &n{0} &+cSpell:&n {1}\r\n", MUDString.PadInt(ch.Wimpy, 4), StringConversion.BonusString(-ch.SavingThrows[4])); buf1 += buf; buf = String.Format("&+BLoad Carried: &n{0} pounds ({1})\r\n", MUDString.PadInt(ch.CarryWeight, 3), StringConversion.WeightString(ch)); buf1 += buf; ch.SendText(buf1); return; }
/// <summary> /// Shows a character's score screen. /// </summary> /// <param name="ch"></param> /// <param name="str"></param> public static void Score(CharData ch, string[] str) { if( ch == null ) return; string text = String.Empty; if (ch == null) { Log.Error("Command.Score(): null ch.", 0); return; } Affect prev; text += "&+WName: &+G" + ch.Name + "&n" + (ch.IsNPC() ? String.Empty : ((PC)ch).Title) + "\r\n"; text += "&+WRace:&n " + MUDString.PadStr(Race.RaceList[ch.GetRace()].ColorName, 16); text += "&+WClass:&n " + MUDString.PadStr(ch.CharacterClass.WholistName, 32) + "&n\r\n"; text += "&+WLevel: " + MUDString.PadInt(ch.Level, 5) + " Played: " + (ch.TimePlayed.Hours) + " hours &+WSex: "; text += System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(ch.GetSexString()) + "\r\n"; if (ch.Fighting == null) { text += "&+WExperience: &+B" + StringConversion.ExperienceString(ch) + "&n\r\n\r\n"; } text += "&+WCurrent/Max Health: [&n&+g" + MUDString.PadInt(ch.Hitpoints, 5) + "&+W / &n&+g" + MUDString.PadInt(ch.GetMaxHit(), 5); text += "&+W] Coins: Carried In Bank\r\n"; text += "&+WCurrent/Max Moves: [&n&+g" + MUDString.PadInt(ch.CurrentMoves, 5) + "&+W / &n&+g" + MUDString.PadInt(ch.MaxMoves, 5); text += "&+W] &+WPlatinum " + MUDString.PadInt(ch.GetPlatinum(), 5) + " "; text += (ch.IsNPC() ? 0 : ((PC)ch).Bank.Platinum) + "\r\n"; text += "Current/Max Mana: [&n&+g" + MUDString.PadInt(ch.CurrentMana, 5) + "&+W / &n&+g" + MUDString.PadInt(ch.MaxMana, 5); text += "&+W] &+YGold " + MUDString.PadInt(ch.GetGold(), 5) + " " + (ch.IsNPC() ? 0 : ((PC)ch).Bank.Gold) + "\r\n"; text += " &n&+wSilver " + MUDString.PadInt(ch.GetSilver(), 5) + " "; text += (ch.IsNPC() ? 0 : ((PC)ch).Bank.Silver) + "\r\n"; text += "&+WFrags: &+W" + MUDString.PadInt((ch.IsNPC() ? 0 : ((PC)ch).Frags), 3) + "&n &n&+yCopper "; text += MUDString.PadInt(ch.GetCopper(), 5) + " " + (ch.IsNPC() ? 0 : ((PC)ch).Bank.Copper) + "\r\n"; if (!ch.IsNPC()) { text += "&+WTotal Deaths: &+W" + MUDString.PadInt(((PC)ch).MobDeaths + ((PC)ch).PlayerDeaths, 5) + "&n &+WMobs Killed: &+W"; text += MUDString.PadInt(((PC)ch).MobKills, 5) + "&n\r\n&+WPlayers Killed: &+W" + MUDString.PadInt(((PC)ch).PlayerKills, 5); text += "&n &+WPlayer Deaths: &+W" + MUDString.PadInt(((PC)ch).PlayerDeaths, 5) + "&n\r\n"; } if (!ch.IsNPC()) { int divisor = ((PC)ch).Created.Quantity; if (divisor == 0) divisor = 1; text += String.Format("&+WItems Created: &n{0} &+WTotal Value: &n{1} &+WBest: &n{2} &+WAvg: &n{3}\r\n", MUDString.PadInt(((PC)ch).Created.Quantity, 5), MUDString.PadInt(((PC)ch).Created.TotalCost, 5), MUDString.PadInt(((PC)ch).Created.MaxCost, 5), MUDString.PadInt((((PC)ch).Created.TotalCost / divisor), 5)); divisor = ((PC)ch).Destroyed.Quantity; if (divisor == 0) divisor = 1; text += String.Format("&+WItems Destroyed: &n{0} &+WTotal Value: &n{1} &+WBest: &n{2} &+WAvg: &n{3}\r\n", MUDString.PadInt(((PC)ch).Destroyed.Quantity, 5), MUDString.PadInt(((PC)ch).Destroyed.TotalCost, 5), MUDString.PadInt(((PC)ch).Destroyed.MaxCost, 5), MUDString.PadInt((((PC)ch).Destroyed.TotalCost / divisor), 5)); } if (!ch.IsNPC()) { text += "&+WTotal Score: &+W" + ((PC)ch).Score + "&n\r\n"; } if (ch.IsClass(CharClass.Names.monk) || ch.IsClass(CharClass.Names.mystic)) { text += "&+WTradition: &+B" + TraditionData.Names[((PC)ch).Tradition] + "&n\r\n"; text += "&+WTraining Points: &+B" + (ch.IsNPC() ? 0 : ((PC)ch).SkillPoints) + "&n\r\n"; } if (ch.Followers != null && ch.Followers.Count > 0) { text += "&+BFollowers:&n\r\n"; foreach (CharData follower in ch.Followers) { if (follower == null) { continue; } text += follower.ShowNameTo(ch, true) + " &n\r\n"; } text += "\r\n"; } if (ch.IsAffected( Affect.AFFECT_POISON)) { text += "&+GYou are poisoned.&n\r\n"; } if ((ch.IsAffected( Affect.AFFECT_DETECT_MAGIC) || ch.IsImmortal()) && MUDString.StringsNotEqual(BitvectorFlagType.AffectString(ch.AffectedBy, true), "none")) { text += "&+BEnchantments: &+W" + BitvectorFlagType.AffectString(ch.AffectedBy, true) + "&n\r\n\r\n"; } if (ch.Affected != null) { bool printed = false; prev = null; foreach (Affect affect in ch.Affected) { if (!printed) { text += "&+BActive Spells:&+W\r\n"; printed = true; } /* Show only new affects to mortals. */ if (prev != null && prev.Value == affect.Value && prev.Type == affect.Type && !ch.IsImmortal()) { prev = affect; continue; } prev = affect; if (affect.Type == Affect.AffectType.skill && !String.IsNullOrEmpty(affect.Value) && ch.IsImmortal()) { text += MUDString.CapitalizeANSIString( Skill.SkillList[affect.Value].Name ); } else if (affect.Type == Affect.AffectType.skill && !String.IsNullOrEmpty(affect.Value)) { continue; } else if (affect.Type == Affect.AffectType.spell && !String.IsNullOrEmpty(affect.Value)) { text += MUDString.CapitalizeANSIString(Spell.SpellList[affect.Value].Name); } else if (affect.Type == Affect.AffectType.song && !String.IsNullOrEmpty(affect.Value)) { text += MUDString.CapitalizeANSIString(affect.Value); } else { text += "Something"; } if (ch.IsImmortal()) { foreach (AffectApplyType apply in affect.Modifiers) { text += " modifies " + StringConversion.AffectApplyString(apply.Location) + " by " + apply.Amount; } text += " for " + affect.Duration + " hours with bits " + affect.AffectString(false) + ".\r\n"; } else { if (affect.Duration == 0 && ch.IsAffected( Affect.AFFECT_DETECT_MAGIC)) { text += " (fading rapidly)\r\n"; } else if (affect.Duration == 1 && ch.IsAffected( Affect.AFFECT_DETECT_MAGIC)) { text += " (fading)\r\n"; } else { text += "\r\n"; } } } } text += "&n"; ch.SendText(text); return; }
public static void Shift(CharData ch, string[] str) { if( ch == null ) return; if (ch.GetRace() != Race.RACE_GITHYANKI && !ch.IsImmortal()) { ch.SendText("You lack that abillity!\r\n"); return; } if (str.Length < 1 || String.IsNullOrEmpty(str[0])) { ch.SendText("Shift to where?\r\n"); return; } if (ch.Fighting) { ch.SendText("You can't break off your fight.\r\n"); return; } if ((ch.HasInnateTimer(InnateTimerData.Type.shift_astral) && !MUDString.IsPrefixOf(str[0], "astral")) || (ch.HasInnateTimer(InnateTimerData.Type.shift_prime) && !MUDString.IsPrefixOf(str[0], "prime"))) { ch.SendText("You need to rest a _bitvector first.\r\n"); return; } Area area = ch.InRoom.Area; Spell spell = StringLookup.SpellLookup("plane shift"); if (!spell) { ch.SendText("Something seems to be blocking your ability to shift."); Log.Error("Shift: 'plane shift' spell not found. Check the spells file."); return; } spell.Invoke(ch, ch.Level, new Target(str[0])); // if it failed, don't lag or add a timer if (area == ch.InRoom.Area) return; if (!ch.IsImmortal()) { if (!MUDString.IsPrefixOf(str[0], "astral")) ch.AddInnateTimer(InnateTimerData.Type.shift_astral, 8); else if (!MUDString.IsPrefixOf(str[0], "prime")) ch.AddInnateTimer(InnateTimerData.Type.shift_prime, 8); } ch.WaitState(14); }
/// <summary> /// Checks for magic resistance, also known as "shrug" -- as in "shrugging off the effects". /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <returns></returns> public static bool CheckShrug( CharData ch, CharData victim ) { int chance; if( !victim.HasInnate( Race.RACE_SHRUG ) ) { return false; } switch( victim.GetRace() ) { // Default at 99 to show races not listed here. case Race.RACE_GREYELF: chance = 25; break; case Race.RACE_GITHZERAI: chance = 10; break; case Race.RACE_DROW: chance = 40; break; case Race.RACE_RAKSHASA: chance = 15; break; case Race.RACE_GITHYANKI: chance = 20; break; // Demons, devils, etc. default: chance = 25 + ( victim.Level ) / 2; break; } if( MUDMath.NumberPercent() < chance ) { SocketConnection.Act( "&+MYour spell flows around &n$N&+M, leaving $M unharmed!&n", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "&+M$N&+M's spell flows around you, leaving you unharmed!&n", victim, null, ch, SocketConnection.MessageTarget.character ); SocketConnection.Act( "&+M$N&+M's spell flows around $n&+M, leaving $m unharmed!&n", victim, null, ch, SocketConnection.MessageTarget.room_vict ); return true; } return false; }
/// <summary> /// Creates a duplicate of a mobile minus its inventory. /// </summary> /// <param name="parent"></param> /// <param name="clone"></param> public static void CloneMobile( CharData parent, CharData clone ) { int i; if( parent == null || clone == null || !parent.IsNPC() ) return; // Fix values. clone.Name = parent.Name; clone.ShortDescription = parent.ShortDescription; clone.FullDescription = parent.FullDescription; clone.Description = parent.Description; clone.Gender = parent.Gender; clone.CharacterClass = parent.CharacterClass; clone.SetPermRace( parent.GetRace() ); clone.Level = parent.Level; clone.TrustLevel = 0; clone.SpecialFunction = parent.SpecialFunction; clone.SpecialFunctionNames = parent.SpecialFunctionNames; clone.Timer = parent.Timer; clone.Wait = parent.Wait; clone.Hitpoints = parent.Hitpoints; clone.MaxHitpoints = parent.MaxHitpoints; clone.CurrentMana = parent.CurrentMana; clone.MaxMana = parent.MaxMana; clone.CurrentMoves = parent.CurrentMoves; clone.MaxMoves = parent.MaxMoves; clone.SetCoins( parent.GetCopper(), parent.GetSilver(), parent.GetGold(), parent.GetPlatinum() ); clone.ExperiencePoints = parent.ExperiencePoints; clone.ActionFlags = parent.ActionFlags; clone.Affected = parent.Affected; clone.CurrentPosition = parent.CurrentPosition; clone.Alignment = parent.Alignment; clone.Hitroll = parent.Hitroll; clone.Damroll = parent.Damroll; clone.Wimpy = parent.Wimpy; clone.Deaf = parent.Deaf; clone.Hunting = parent.Hunting; clone.Hating = parent.Hating; clone.Fearing = parent.Fearing; clone.Resistant = parent.Resistant; clone.Immune = parent.Immune; clone.Susceptible = parent.Susceptible; clone.CurrentSize = parent.CurrentSize; clone.PermStrength = parent.PermStrength; clone.PermIntelligence = parent.PermIntelligence; clone.PermWisdom = parent.PermWisdom; clone.PermDexterity = parent.PermDexterity; clone.PermConstitution = parent.PermConstitution; clone.PermAgility = parent.PermAgility; clone.PermCharisma = parent.PermCharisma; clone.PermPower = parent.PermPower; clone.PermLuck = parent.PermLuck; clone.ModifiedStrength = parent.ModifiedStrength; clone.ModifiedIntelligence = parent.ModifiedIntelligence; clone.ModifiedWisdom = parent.ModifiedWisdom; clone.ModifiedDexterity = parent.ModifiedDexterity; clone.ModifiedConstitution = parent.ModifiedConstitution; clone.ModifiedAgility = parent.ModifiedAgility; clone.ModifiedCharisma = parent.ModifiedCharisma; clone.ModifiedPower = parent.ModifiedPower; clone.ModifiedLuck = parent.ModifiedLuck; clone.ArmorPoints = parent.ArmorPoints; //clone._mpactnum = parent._mpactnum; for (i = 0; i < 6; i++) { clone.SavingThrows[i] = parent.SavingThrows[i]; } // Now add the affects. foreach (Affect affect in parent.Affected) { clone.AddAffect(affect); } }
/// <summary> /// Checks whether char is resistant to the supplied damage type. /// </summary> /// <param name="ch"></param> /// <param name="bit"></param> /// <returns></returns> public static bool CheckResistant( CharData ch, Race.DamageType bit ) { return ( Macros.IsSet( (int)ch.Resistant | (int)Race.RaceList[ ch.GetRace() ].Resistant, (int)bit ) ); }
/// <summary> /// Checks whether char is vulnerable to the supplied damage type. /// </summary> /// <param name="ch"></param> /// <param name="bit"></param> /// <returns></returns> public static bool CheckVulnerable(CharData ch, Race.DamageType bit) { return (Macros.IsSet((int)ch.Vulnerable | (int)Race.RaceList[ch.GetRace()].Vulnerable, (int)bit)); }
/// <summary> /// True if char can see victim. /// /// This is only a straightford all-or-none vision checker. /// /// If you need more granularity, use Command.HowSee which returns an enum /// based on the level of visibility but otherwise functions similarly. /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <returns></returns> public static bool CanSee( CharData ch, CharData victim ) { if( ch == null ) { Log.Error( "CharData.CanSee: called with null ch.", 0 ); return false; } if( victim == null ) { Log.Error( "CharData.CanSee: called with null victim.", 0 ); return false; } if( ch == victim ) { return true; } /* All mobiles cannot see wizinvised immorts */ if (ch.IsNPC() && !ch.IsNPC() && ch.HasActionBit(PC.PLAYER_WIZINVIS)) { return false; } if (!ch.IsNPC() && ch.HasActionBit(PC.PLAYER_WIZINVIS) && ch.GetTrust() < ch.Level) { return false; } if (!ch.IsNPC() && ch.HasActionBit(PC.PLAYER_GODMODE)) { return true; } if( ch.IsAffected( Affect.AFFECT_BLIND ) ) { return false; } if (ch.InRoom == null) { Log.Error("CanSee called by player " + ch.Name + " with null room."); return false; } if( ch.InRoom.IsDark() && !ch.HasInnate( Race.RACE_ULTRAVISION ) && !ch.IsAffected( Affect.AFFECT_ULTRAVISION ) && !ch.HasInnate( Race.RACE_INFRAVISION ) && !ch.IsAffected(Affect.AFFECT_INFRAVISION ) ) { return false; } if (ch.CurrentPosition == Position.dead) { return true; } if ((victim.IsAffected(Affect.AFFECT_INVISIBLE) || victim.IsAffected(Affect.AFFECT_MINOR_INVIS)) && !ch.HasInnate( Race.RACE_DETECT_INVIS ) && !ch.IsAffected(Affect.AFFECT_DETECT_INVIS ) && !(ch.IsAffected(Affect.AFFECT_ELEM_SIGHT) && ( ch.GetRace() == Race.RACE_AIR_ELE || ch.GetRace() == Race.RACE_WATER_ELE || ch.GetRace() == Race.RACE_FIRE_ELE || ch.GetRace() == Race.RACE_EARTH_ELE ) ) ) { return false; } if( victim.IsAffected( Affect.AFFECT_HIDE ) && !ch.HasInnate( Race.RACE_DETECT_HIDDEN ) && !ch.IsAffected( Affect.AFFECT_DETECT_HIDDEN ) && !ch.Fighting ) { return false; } return true; }
/// <summary> /// Checks whether char is immune to the supplied damage type. /// </summary> /// <param name="ch"></param> /// <param name="bit"></param> /// <returns></returns> public static bool CheckImmune( CharData ch, Race.DamageType bit ) { return ( Macros.IsSet( (int)ch.Immune | (int)Race.RaceList[ ch.GetRace() ].Immune, (int)bit ) ); }
/// <summary> /// Checks whether the caller is aggressive toward the victim. /// </summary> /// <param name="victim"></param> /// <returns></returns> public bool IsAggressive(CharData victim) { CharData ch = this; Guild guild = null; if (victim == null) { Log.Error("IsAggressive: called with null ch or victim.", 0); return false; } if (ch == victim) { return false; } if (MUDString.NameContainedIn("_guildgolem_", ch.Name)) { foreach (Guild it in Database.GuildList) { guild = it; if (guild.ID == Guild.GolemGuildID(ch)) break; } if (guild != null && guild.Ostracized.Length != 0) { if (MUDString.NameContainedIn(victim.Name, guild.Ostracized)) return true; } } if (MUDString.NameContainedIn(Race.RaceList[victim.GetRace()].Name, Race.RaceList[ch.GetOrigRace()].Hate)) { Log.Trace("Returning true for IsAggressive due to race hatred."); return true; } if (ch.HasActionBit(MobTemplate.ACT_AGGROGOOD) && victim.IsGood()) { Log.Trace("Returning true for IsAggressive due to aggro good and good victim."); return true; } if (ch.HasActionBit(MobTemplate.ACT_AGGROEVIL) && victim.IsEvil()) { Log.Trace("Returning true for IsAggressive due to aggro evil and evil victim."); return true; } if (ch.HasActionBit(MobTemplate.ACT_AGGRONEUT) && victim.IsNeutral()) { Log.Trace("Returning true for IsAggressive due to aggro neutral and neutral victim."); return true; } if (ch.HasActionBit(MobTemplate.ACT_AGGROEVILRACE) && victim.GetRacewarSide() == Race.RacewarSide.evil) { Log.Trace("Returning true for IsAggressive due to aggro evil race and victim evil racewar."); return true; } if (ch.HasActionBit(MobTemplate.ACT_AGGROGOODRACE) && victim.GetRacewarSide() == Race.RacewarSide.good) { Log.Trace("Returning true for IsAggressive due to aggro good race and victim good racewar."); return true; } if (ch.HasActionBit(MobTemplate.ACT_AGGRESSIVE)) { Log.Trace("Returning true for IsAggressive due to aggressive flag on ch."); return true; } return ch.IsHating(victim); }
/// <summary> /// Regeneration and natural healing. /// </summary> /// <param name="ch"></param> /// <returns></returns> public static int HitGain( CharData ch ) { int gain; int percent = 0; if( ch == null ) { Log.Error( "HitGain(): null ch", 0 ); return 0; } // They aren't going to gain hits in a no heal room... if (ch.InRoom.HasFlag(RoomTemplate.ROOM_NO_HEAL)) { return 0; } gain = 1; percent = 100; if (ch.CheckSkill("fast healing", PracticeType.none)) { gain += 1; } if (MUDMath.NumberPercent() < 3) { ch.PracticeSkill("fast healing"); } switch( ch.CurrentPosition ) { case Position.sleeping: gain += 3; break; case Position.reclining: gain += 2; break; case Position.resting: gain += 1; break; case Position.fighting: gain = 0; break; } if( ch.HasInnate( Race.RACE_REGENERATE ) ) { // Automatically one extra hp, two at level 30. gain += 1 + (ch.Level / 30); // One percent chance of gaining another per level. percent += (ch.Level); } // Hunger and thirst for PCs. if (!ch.IsNPC()) { if (((PC)ch).Hunger == 0) { gain /= 2; gain -= 1; percent -= 25; ch.SendText("&nYou double over from &+Rhunger pains&n!\r\n"); } if (((PC)ch).Thirst == 0) { gain /= 2; gain -= 1; percent -= 25; ch.SendText("&nYou suffer from severe &+cth&+Ci&n&+cr&+Cst&n!\r\n"); } } if( ch.IsAffected( Affect.AFFECT_POISON ) ) { gain /= 4; percent /= 2; } if( gain == 0 ) if( MUDMath.NumberPercent() < percent ) gain = 1; // Heal rooms heal you a little quicker if (ch.InRoom.HasFlag(RoomTemplate.ROOM_HEAL)) { gain += Math.Max(1, gain / 2); } if( ( ch.InRoom.TerrainType != TerrainType.underwater_has_ground && ch.InRoom.TerrainType != TerrainType.unswimmable_water && ch.InRoom.TerrainType != TerrainType.swimmable_water && ch.HasInnate( Race.RACE_WATERBREATH ) && MUDString.StringsNotEqual( Race.RaceList[ ch.GetRace() ].Name, "Object" ) && ch.GetRace() != Race.RACE_GOD ) || ( ch.InRoom.TerrainType == TerrainType.underwater_has_ground && ( !ch.IsImmortal() && !ch.IsAffected( Affect.AFFECT_BREATHE_UNDERWATER ) && !ch.HasInnate( Race.RACE_WATERBREATH ) ) ) ) gain = 0; return Math.Min( gain, ch.GetMaxHit() - ch.Hitpoints ); }
/// <summary> /// This function's main access commands are wear, wield, and hold, which have the /// following flow: /// wear: Command.Wear, wear_obj, equip_hand /// wield: Command.Wield, equip_hand /// hold: Command.Hold, equip_hand /// /// We assume by this point that the character is physically able to use the item and will be able /// to equip it however specified. Those checks are performed by WearObject(), Equip(), and Hold(). /// </summary> /// <param name="ch"></param> /// <param name="obj"></param> /// <param name="type"></param> /// <returns></returns> public static bool EquipInHand(CharData ch, Object obj, int type) { int weight = 0; ObjTemplate.WearLocation firstAvail = ObjTemplate.WearLocation.none; ObjTemplate.WearLocation secondAvail = ObjTemplate.WearLocation.none; ObjTemplate.WearLocation lastAvail = ObjTemplate.WearLocation.none; Object hand1 = GetEquipmentOnCharacter(ch, ObjTemplate.WearLocation.hand_one); Object hand2 = GetEquipmentOnCharacter(ch, ObjTemplate.WearLocation.hand_two); Object hand3 = GetEquipmentOnCharacter(ch, ObjTemplate.WearLocation.hand_three); Object hand4 = GetEquipmentOnCharacter(ch, ObjTemplate.WearLocation.hand_four); if (hand1 && (hand1._itemType == ObjTemplate.ObjectType.weapon || hand1._itemType == ObjTemplate.ObjectType.ranged_weapon)) weight += hand1.GetWeight(); if (hand2 && (hand2._itemType == ObjTemplate.ObjectType.weapon || hand2._itemType == ObjTemplate.ObjectType.ranged_weapon)) weight += hand2.GetWeight(); if (hand3 && (hand3._itemType == ObjTemplate.ObjectType.weapon || hand3._itemType == ObjTemplate.ObjectType.ranged_weapon)) weight += hand3.GetWeight(); if (hand4 && (hand4._itemType == ObjTemplate.ObjectType.weapon || hand4._itemType == ObjTemplate.ObjectType.ranged_weapon)) weight += hand4.GetWeight(); if (ch.GetRace() != Race.RACE_THRIKREEN) { if (hand3) Log.Error("non-thrikreen wielding item in hand3", 0); if (hand4) Log.Error("non-thrikreen wielding item in hand4", 0); } // Find number of hand slots used and first available hand. // Be sure to handle twohanded stuff. if (hand4 && hand4.HasFlag(ObjTemplate.ITEM_TWOHANDED)) { Log.Error("Twohanded weapon in fourth hand -- this is not possible.", 0); } if (hand3 && hand3.HasFlag(ObjTemplate.ITEM_TWOHANDED)) { if (hand4) { Log.Error("Twohanded weapon in third hand with fourth hand holding twohanded weapon -- this is not possible, all twohanded must have a blank hand after it.", 0); } hand4 = hand3; } if (hand2 && hand2.HasFlag(ObjTemplate.ITEM_TWOHANDED)) { if (hand3) { Log.Error("Twohanded weapon in second hand with third hand holding twohanded weapon -- this is not possible, all twohanded must have a blank hand after it.", 0); } hand2 = hand3; } if (!ch.HasInnate(Race.RACE_EXTRA_STRONG_WIELD)) { if (hand1 && hand1.HasFlag(ObjTemplate.ITEM_TWOHANDED)) { if (hand2) { Log.Error("Twohanded weapon in second hand with first hand holding twohanded weapon -- this is not possible, all twohanded must have a blank hand after it.", 0); } hand2 = hand1; } } if (obj.HasFlag(ObjTemplate.ITEM_TWOHANDED) && !ch.HasInnate(Race.RACE_EXTRA_STRONG_WIELD)) { if (ch.GetRace() == Race.RACE_THRIKREEN && !hand4) { firstAvail = ObjTemplate.WearLocation.hand_four; lastAvail = ObjTemplate.WearLocation.hand_four; } if (ch.GetRace() == Race.RACE_THRIKREEN && !hand3) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_three; secondAvail = firstAvail; firstAvail = ObjTemplate.WearLocation.hand_three; } if (!hand2) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_two; secondAvail = firstAvail; firstAvail = ObjTemplate.WearLocation.hand_two; } if (!hand1) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_one; secondAvail = firstAvail; firstAvail = ObjTemplate.WearLocation.hand_one; } if (firstAvail == 0) { ch.SendText("Your hands are full!\r\n"); return false; } if (secondAvail == 0) { ch.SendText("You need two hands free to wield that!\r\n"); return false; } } else if (obj.HasFlag(ObjTemplate.ITEM_TWOHANDED)) { if (ch.GetRace() == Race.RACE_THRIKREEN && !hand4) { firstAvail = ObjTemplate.WearLocation.hand_four; } if (ch.GetRace() == Race.RACE_THRIKREEN && !hand3) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_three; secondAvail = firstAvail; firstAvail = ObjTemplate.WearLocation.hand_three; } if (!hand2) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_two; secondAvail = firstAvail; firstAvail = ObjTemplate.WearLocation.hand_two; } if (!hand1) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_one; secondAvail = ObjTemplate.WearLocation.hand_one; firstAvail = ObjTemplate.WearLocation.hand_one; } if (firstAvail == 0) { ch.SendText("Your hands are full!\r\n"); return false; } if (secondAvail == 0) { ch.SendText("You need two hands free to wield that!\r\n"); return false; } } else { if (ch.GetRace() == Race.RACE_THRIKREEN && !hand4) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_four; firstAvail = ObjTemplate.WearLocation.hand_four; } if (ch.GetRace() == Race.RACE_THRIKREEN && !hand3) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_three; firstAvail = ObjTemplate.WearLocation.hand_three; } if (!hand2) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_two; firstAvail = ObjTemplate.WearLocation.hand_two; } if (hand1 == null) { if (lastAvail == 0) lastAvail = ObjTemplate.WearLocation.hand_one; firstAvail = ObjTemplate.WearLocation.hand_one; } if (firstAvail == 0) { ch.SendText("Your hands are full!\r\n"); return false; } } // Successful hand availability, send message and ready the item. // Twohanded shields, held items, and lights are equipped primary. // This could annoy ogres/thris but twohanded versions of these items // are so rare it's not likely to be an issue. switch (type) { case EQUIP_HOLD: if (!obj.HasFlag(ObjTemplate.ITEM_TWOHANDED)) ch.EquipObject(ref obj, lastAvail); else ch.EquipObject(ref obj, firstAvail); break; case EQUIP_SHIELD: SocketConnection.Act("You strap $p&n to your arm.", ch, obj, null, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n straps $p&n to $s arm.", ch, obj, null, SocketConnection.MessageTarget.room); if (!obj.HasFlag(ObjTemplate.ITEM_TWOHANDED)) ch.EquipObject(ref obj, lastAvail); else ch.EquipObject(ref obj, firstAvail); break; case EQUIP_LIGHT: if (obj._itemType == ObjTemplate.ObjectType.light && obj._values[2] != 0) { SocketConnection.Act("You &n&+rli&+Rght&n $p&n and hold it before you.", ch, obj, null, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n &+Rlig&n&+rhts&n $p&n and holds it before $m.", ch, obj, null, SocketConnection.MessageTarget.room); } else { SocketConnection.Act("You hold the &+Lspent&n remains of $p&n.", ch, obj, null, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n holds the spent husk of $p&n.", ch, obj, null, SocketConnection.MessageTarget.room); } if (!obj.HasFlag(ObjTemplate.ITEM_TWOHANDED)) ch.EquipObject(ref obj, lastAvail); else ch.EquipObject(ref obj, firstAvail); break; case EQUIP_WIELD: // Have to check for dual wield skill, and for total weight of weapons. if (firstAvail != ObjTemplate.WearLocation.hand_one) { // Those without dual wield cannot wield anything in their second hand. Include thrikreen if (!ch.IsNPC() && !ch.HasSkill("dual wield")) { ch.SendText("You lack the skills to wield a weapon in anything but your primary hand.\r\n"); return false; } } if ((weight + obj.GetWeight()) > StrengthModifier.Table[ch.GetCurrStr()].WieldWeight) { SocketConnection.Act("Your meager strength is overwhelmed by $p&n.", ch, obj, null, SocketConnection.MessageTarget.character); return false; } SocketConnection.Act("You wield $p&n.", ch, obj, null, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n brandishes $p&n.", ch, obj, null, SocketConnection.MessageTarget.room); ch.EquipObject(ref obj, firstAvail); break; } // Objects with a trap activated on wear. if (obj._trap != null && obj._trap.CheckTrigger( Trap.TriggerType.wear)) { ch.SetOffTrap(obj); if (ch.CurrentPosition == Position.dead) return false; } return true; }
/// <summary> /// Processes a blur-type attack. Returns true if the victim died. /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <returns></returns> public static bool BlurAttack( CharData ch, CharData victim ) { if (ch == null) return false; int numAttacks; int count; if( ch.IsAffected( Affect.AFFECT_CASTING ) ) { return false; } if( ch.IsAffected( Affect.AFFECT_BLUR ) && MUDMath.NumberPercent() < 25 ) { SocketConnection.Act( "$n&n moves with a BLUR of speed!", ch, null, null, SocketConnection.MessageTarget.room ); SocketConnection.Act( "You move with a BLUR of speed!", ch, null, null, SocketConnection.MessageTarget.character ); for( count = 0, numAttacks = 4; count < numAttacks && victim.CurrentPosition > Position.dead; ++count ) { SingleAttack( ch, victim, String.Empty, ObjTemplate.WearLocation.hand_one ); } } else { Object wield; if( MUDMath.NumberPercent() > 10 ) return false; if( ch.IsClass(CharClass.Names.hunter) || ch.IsClass(CharClass.Names.ranger )) numAttacks = 2; else if( ch.GetRace() == Race.RACE_OGRE || ch.GetRace() == Race.RACE_CENTAUR ) numAttacks = 4; else numAttacks = 9; if( MUDMath.NumberPercent() < ch.GetCurrLuck() ) numAttacks++; if( MUDMath.NumberPercent() < victim.GetCurrLuck() ) numAttacks--; /* 9716 is the index number for the dagger of the wind. */ // HORRIBLE HORRIBLE TODO: FIXME: BUG: Never hard-code item index numbers. if( ( wield = Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_one ) ) && wield.ObjIndexData.IndexNumber == 9716 ) { SocketConnection.Act( "&+c$n&+c's $p&n &+cbegins to move with the &+Wspeed&+c of a &+lstorm&+c!&n", ch, wield, null, SocketConnection.MessageTarget.room ); SocketConnection.Act( "Your $p&n &+cbegins to move with the &+Wspeed&+c of a &+lstorm&+c!&n", ch, wield, null, SocketConnection.MessageTarget.character ); for( count = 0; count < numAttacks && victim.CurrentPosition > Position.dead; ++count ) { SingleAttack(ch, victim, String.Empty, ObjTemplate.WearLocation.hand_one); } return ( victim.CurrentPosition > Position.dead ); } if( ( wield = Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_two ) ) && wield.ObjIndexData.IndexNumber == 9716 ) { SocketConnection.Act( "&+c$n&+c's $p&n &+cbegins to move with the &+Wspeed&+c of a &+lstorm&+c!&n", ch, wield, null, SocketConnection.MessageTarget.room ); SocketConnection.Act( "Your $p&n &+cbegins to move with the &+Wspeed&+c of a &+lstorm&+c!&n", ch, wield, null, SocketConnection.MessageTarget.character ); for( count = 0; count < numAttacks && victim.CurrentPosition > Position.dead; ++count ) { SingleAttack(ch, victim, String.Empty, ObjTemplate.WearLocation.hand_two); } return ( victim.CurrentPosition > Position.dead ); } if( ( wield = Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_three ) ) && wield.ObjIndexData.IndexNumber == 9716 ) { SocketConnection.Act( "&+c$n&+c's $p&n &+cbegins to move with the &+Wspeed&+c of a &+lstorm&+c!&n", ch, wield, null, SocketConnection.MessageTarget.room ); SocketConnection.Act( "Your $p&n &+cbegins to move with the &+Wspeed&+c of a &+lstorm&+c!&n", ch, wield, null, SocketConnection.MessageTarget.character ); for( count = 0; count < numAttacks && victim.CurrentPosition > Position.dead; ++count ) { SingleAttack(ch, victim, String.Empty, ObjTemplate.WearLocation.hand_three); } return ( victim.CurrentPosition > Position.dead ); } if( ( wield = Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_four ) ) && wield.ObjIndexData.IndexNumber == 9716 ) { SocketConnection.Act( "&+c$n&+c's $p&n &+cbegins to move with the &+Wspeed&+c of a &+lstorm&+c!&n", ch, wield, null, SocketConnection.MessageTarget.room ); SocketConnection.Act( "Your $p&n &+cbegins to move with the &+Wspeed&+c of a &+lstorm&+c!&n", ch, wield, null, SocketConnection.MessageTarget.character ); for( count = 0; count < numAttacks && victim.CurrentPosition > Position.dead; ++count ) { SingleAttack(ch, victim, String.Empty, ObjTemplate.WearLocation.hand_four); } return ( victim.CurrentPosition > Position.dead ); } } return false; }
/// <summary> /// Inflict damage from a single hit. This could use some cleanup since it's way too unwieldy at more than 600 lines. /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <param name="dam"></param> /// <param name="skill"></param> /// <param name="weapon"></param> /// <param name="damType"></param> /// <returns></returns> public static bool InflictDamage(CharData ch, CharData victim, int dam, string skill, ObjTemplate.WearLocation weapon, AttackType.DamageType damType) { if (ch == null || victim == null) { return false; } Object obj; bool critical = false; if( victim.CurrentPosition == Position.dead || victim.Hitpoints < -10 ) { return true; } /* * Stop up any residual loopholes. */ if( ( dam > 1276 ) && ch.Level < Limits.LEVEL_AVATAR ) { string text; if (ch.IsNPC() && ch.Socket) { text = String.Format("Damage: {0} from {1} by {2}: > 1276 points with {3} damage type!", dam, ch.Name, ch.Socket.Original.Name, skill); } else { text = String.Format("Damage: {0} from {1}: > 1276 points with {2} damage type!", dam, ch.IsNPC() ? ch.ShortDescription : ch.Name, skill); } Log.Error( text, 0 ); dam = 1276; } // Remove memorization and meditation bits - Xangis victim.BreakMeditate(); victim.BreakMemorization(); if (victim.IsAffected( Affect.AFFECT_MINOR_PARA)) { SocketConnection.Act( "$n&n disrupts the magic preventing $N&n from moving.", ch, null, victim, SocketConnection.MessageTarget.room_vict ); SocketConnection.Act( "You disrupt the magic preventing $N&n from moving.", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "&+YYou can move again.&n", ch, null, victim, SocketConnection.MessageTarget.victim ); victim.RemoveAffect( Affect.AFFECT_MINOR_PARA ); } bool immune = false; if( victim != ch ) { /* * Certain attacks are forbidden. * Most other attacks are returned. */ victim = CheckGuarding( ch, victim ); if( IsSafe( ch, victim ) ) return false; // is_safe could wipe out victim, as it calls procs if a boss // check and see that victim is still valid if( victim == null ) return true; Crime.CheckAttemptedMurder( ch, victim ); if( victim.CurrentPosition > Position.stunned ) { if( !victim.Fighting ) SetFighting( victim, ch ); // Can't have prone people automatically stand if( victim.CurrentPosition == Position.standing ) victim.CurrentPosition = Position.fighting; if( !ch.Fighting ) SetFighting( ch, victim ); /* * If NPC victim is following, ch might attack victim's master. * No charm check here because charm would be dispelled from * tanking mobile when combat ensues thus ensuring PC charmer is * not harmed. * Check for IsSameGroup wont work as following mobile is not * always grouped with PC charmer * * Added a check for whether ch has switch skill. If not, * much lower chancing of retargetting */ if( ch.IsNPC() && victim.IsNPC() && victim.Master && victim.Master.InRoom == ch.InRoom && MUDMath.NumberBits( 2 ) == 0 ) { StartGrudge( ch, victim.Master, false ); } } /* * More charm stuff. */ if( victim.Master == ch ) { StopFighting( victim, true ); } ch.BreakInvisibility(); /* * Hunting stuff... */ if( dam != 0 && victim.IsNPC() ) { /* StartGrudge is combined StartHating and StartHunting */ StartGrudge( victim, ch, false ); } /* * Damage modifiers. */ // Critical hits for double damage // Average of 5% for those that have average luck // Gnomes could concievably have 10% if( MUDMath.NumberPercent() < ( 2 + ( ch.GetCurrLuck() / 18 ) ) && dam > 0 ) { ch.SendText( "&+WYou score a CRITICAL HIT!&n\r\n" ); dam *= 2; critical = true; } if( victim.IsAffected( Affect.AFFECT_SANCTUARY ) ) dam /= 2; if( victim.IsAffected( Affect.AFFECT_PROTECT_EVIL ) && ch.IsEvil() ) dam -= dam / 8; else if( victim.IsAffected( Affect.AFFECT_PROTECT_GOOD ) && ch.IsGood() ) dam -= dam / 8; // Check stoneskin. People not affected by a stoneskin affect // cannot lose their stoneskin for any reason. This should mean // that mobs will keep their stoneskin and players should always // have a chance to lose it, since no player should ever be // setbit stoneskin. // // The bool value of found is used so that we can have them // take full damage when their stoneskin shatters, but get the // damage reduction if they are either a mob or their stoneskin // wears off that round. // /* Yeah, yeah.. so maybe backstabs shouldn't be aff'd. */ // Actually they should be affected, but they should have a much // higher chance of getting through (say 30-70%). // // Critical hits will now go through stoneskin // automatically if (!critical && victim.IsAffected( Affect.AFFECT_STONESKIN) && ( skill != "backstab" || MUDMath.NumberPercent() < ( 25 + ch.Level ) ) ) { bool found = false; for (int i = (victim.Affected.Count - 1); i >= 0; i--) { if( victim.Affected[i].HasBitvector( Affect.AFFECT_STONESKIN ) ) { // Small chance of shattering the stoneskin on a good hit. // Reduced chance by about 20% if( dam >= 25 && MUDMath.NumberPercent() <= ( dam / 12 ) ) { victim.SendText( "&+LYour stoneskin is shattered by the massive blow!&n\r\n" ); SocketConnection.Act( "$n&n's massive blow shatters $N&n's stoneskin!", ch, null, victim, SocketConnection.MessageTarget.everyone_but_victim ); SocketConnection.Act( "Your massive blow shatters $N&n's stoneskin!", ch, null, victim, SocketConnection.MessageTarget.character ); victim.RemoveAffect(victim.Affected[i]); found = true; } else if( dam > 0 ) // Added check for actual damage { for( int j = 0; j < victim.Affected[i].Modifiers.Count; j++ ) { victim.Affected[i].Modifiers[j].Amount--; if (victim.Affected[i].Modifiers[j].Amount < 1) { victim.RemoveAffect(victim.Affected[i]); victim.SendText("&+LYou feel your skin soften and return to normal.&n\r\n"); } dam /= 15; found = true; } } } } // This means they're Affect.AFFECT_STONESKIN as an innate/permenant. // We will still allow it to shatter, but it will refresh itself // upon a mob update. Because of this, we make it easier to shatter. // No damage reduction when it shatters. if( !found ) { if( dam >= 8 && MUDMath.NumberPercent() <= ( dam / 8 ) ) { victim.SendText( "&+LYour stoneskin is shattered by the massive blow!&n\r\n" ); SocketConnection.Act( "$n&n's massive blow shatters $N&n's stoneskin!", ch, null, victim, SocketConnection.MessageTarget.everyone_but_victim ); SocketConnection.Act( "Your massive blow shatters $N&n's stoneskin!", ch, null, victim, SocketConnection.MessageTarget.character ); victim.RemoveAffect( Affect.AFFECT_STONESKIN ); } else { dam = dam / 15 != 0 ? dam / 15 : 1; } } } if( dam < 0 ) dam = 0; /* * Check for disarm, trip, parry, dodge and shield block. */ if (skill != "barehanded fighting" || skill == "kick") { // Trip and disarm removed because those should be handled // by each individual mob's special function. if( ch.IsNPC() && ch.HasInnate( Race.RACE_WEAPON_WIELD ) && MUDMath.NumberPercent() < Math.Min( 25, Math.Max( 10, ch.Level ) ) && !victim.IsNPC() ) UseMagicalItem( ch ); } } switch( victim.CheckRIS( damType ) ) { case Race.ResistanceType.resistant: dam -= dam / 3; break; case Race.ResistanceType.immune: immune = true; dam = 0; break; case Race.ResistanceType.susceptible: dam += dam / 2; break; case Race.ResistanceType.vulnerable: dam *= 2; break; default: break; } if( ( damType == AttackType.DamageType.wind || damType == AttackType.DamageType.gas || damType == AttackType.DamageType.asphyxiation ) && victim.IsAffected(Affect.AFFECT_DENY_AIR)) { if( MUDMath.NumberPercent() < 50 ) { ch.SendText( "&+CYou deny the damage.&n\r\n" ); immune = true; dam = 0; } else dam -= dam / 5; } if (damType == AttackType.DamageType.fire && victim.IsAffected( Affect.AFFECT_DENY_FIRE)) { if( MUDMath.NumberPercent() < 50 ) { ch.SendText( "&+rYou deny the damage.&n\r\n" ); immune = true; dam = 0; } else dam -= dam / 5; } if( ( damType == AttackType.DamageType.earth || damType == AttackType.DamageType.crushing ) && victim.IsAffected( Affect.AFFECT_DENY_EARTH)) { if( MUDMath.NumberPercent() < 50 ) { ch.SendText( "&+yYou deny the damage.&n\r\n" ); immune = true; dam = 0; } else dam -= dam / 5; } if( ( damType == AttackType.DamageType.water || damType == AttackType.DamageType.acid || damType == AttackType.DamageType.drowning ) && victim.IsAffected( Affect.AFFECT_DENY_WATER)) { if( MUDMath.NumberPercent() < 50 ) { ch.SendText( "&+bYou deny the damage.&n\r\n" ); immune = true; dam = 0; } else dam -= dam / 5; } // Check for protection spells that give 25% damage reduction - Xangis if (damType == AttackType.DamageType.fire && victim.IsAffected( Affect.AFFECT_PROTECT_FIRE)) dam = ( dam * 3 ) / 4; else if (damType == AttackType.DamageType.cold && victim.IsAffected( Affect.AFFECT_PROTECT_COLD)) dam = ( dam * 3 ) / 4; else if (damType == AttackType.DamageType.acid && victim.IsAffected( Affect.AFFECT_PROTECT_ACID)) dam = ( dam * 3 ) / 4; else if (damType == AttackType.DamageType.gas && victim.IsAffected( Affect.AFFECT_PROTECT_GAS)) dam = ( dam * 3 ) / 4; else if (damType == AttackType.DamageType.electricity && victim.IsAffected( Affect.AFFECT_PROTECT_LIGHTNING)) dam = ( dam * 3 ) / 4; // Barkskin protects from 8% of slash and 12% of pierce damage. if (victim.IsAffected( Affect.AFFECT_BARKSKIN)) { if (skill == "1h slashing" || skill == "2h slashing") dam = dam * 11 / 12; else if (skill == "1h piercing" || skill == "2h piercing") dam = dam * 7 / 8; } // Check for vampiric touch for anti-paladins and vampires if( weapon == ObjTemplate.WearLocation.hand_one || weapon == ObjTemplate.WearLocation.hand_two || weapon == ObjTemplate.WearLocation.hand_three || weapon == ObjTemplate.WearLocation.hand_four ) { if( ( ( ch.IsClass(CharClass.Names.antipaladin) || ch.GetRace() == Race.RACE_VAMPIRE ) && skill == "barehanded fighting" && !Object.GetEquipmentOnCharacter(ch, weapon)) || (ch.IsAffected( Affect.AFFECT_VAMP_TOUCH) && ( !( obj = Object.GetEquipmentOnCharacter( ch, weapon ) ) || obj.HasAffect( Affect.AFFECT_VAMP_TOUCH ) ) ) ) { ch.Hitpoints += dam / 3; if( ch.Hitpoints > ( ch.GetMaxHit() + 50 + ch.Level * 5 ) ) { ch.Hitpoints = ch.GetMaxHit() + 50 + ch.Level * 5; } } } /* PC to PC damage quartered. * NPC to PC damage divided by 3. */ if( dam > 0 && !victim.IsNPC() && victim != ch ) { if( !ch.IsNPC() ) dam /= 4; else dam /= 3; } /* * Just a check for anything that is excessive damage * Send a log message, keeping the imms on their toes * Changed this from 300 to 250 'cause hitters get more than one * attack/round and w/haste that's 1000 possible in the time one fist * goes off. That's more than the fist might do and it has to be * memmed. */ if (dam > 250 && skill != "backstab" ) { string buf4; if (!string.IsNullOrEmpty(skill)) { buf4 = String.Format("Excessive damage: {0} attacking {1} for {2}, skill = {3}({4}).", ch.Name, victim.Name, dam, Skill.SkillList[skill].DamageText, skill); } else { buf4 = String.Format("Excessive damage: {0} attacking {1} for {2}, unknown damage type.", ch.Name, victim.Name, dam); } Log.Trace( buf4 ); } /* * We moved DamageMessage out of the victim != ch if above * so self damage would show. Other valid type_undefined * damage is ok to avoid like mortally wounded damage */ if (!String.IsNullOrEmpty(skill)) { SendDamageMessage(ch, victim, dam, skill, weapon, immune); } victim.Hitpoints -= dam; /* Check for HOLY_SACRFICE and BATTLE_ECSTASY */ if( dam > 0 && victim != ch ) { CharData groupChar; if (victim.IsAffected( Affect.AFFECT_HOLY_SACRIFICE) && victim.GroupLeader) { for( groupChar = victim.GroupLeader; groupChar; groupChar = groupChar.NextInGroup ) { if( groupChar == victim || groupChar.InRoom != ch.InRoom ) continue; groupChar.Hitpoints += dam / 5; if (groupChar.Hitpoints > groupChar.GetMaxHit() + 50 + groupChar.Level * 5) { groupChar.Hitpoints = groupChar.GetMaxHit() + 50 + groupChar.Level * 5; } } //end for loop } //end if holy sac if( ch.GroupLeader != null ) { for( groupChar = ch.GroupLeader; groupChar != null; groupChar = groupChar.NextInGroup ) { if( groupChar == victim || groupChar.InRoom != ch.InRoom ) continue; if( groupChar.IsAffected( Affect.AFFECT_BATTLE_ECSTASY ) ) { groupChar.Hitpoints += dam / 20; if( groupChar.Hitpoints > groupChar.GetMaxHit() + 50 + groupChar.Level * 5 ) groupChar.Hitpoints = groupChar.GetMaxHit() + 50 + groupChar.Level * 5; } // end if battle ecstasy } //end for loop } //end if grouped } //end if // Make sure if they got an instant kill roll that the victim dies. if (skill == "instant kill") { if( victim.GetRace() != Race.RACE_DEVIL && victim.GetRace() != Race.RACE_DEMON && victim.GetRace() != Race.RACE_GOD ) victim.Hitpoints = -20; } /* Added damage exp! */ // chance added because people level faster and faster as they get higher level... // to be worked out when exp is redone. // you can now only get damage exp on mobs that con easy or better // and there's only a 25% chance per hit of you evern being eligible for damage exp. if( MUDMath.NumberPercent() < 25 && victim.Level >= ( ch.Level - 3 ) ) ch.GainExperience( Math.Max( 1, dam / 20 ) ); if( !victim.IsNPC() && victim.Level >= Limits.LEVEL_AVATAR && victim.Hitpoints < 1 ) victim.Hitpoints = 1; /* * Magic shields that retaliate * * Apparently two people with the same sort of shield do not * take damage from each other */ if( ( dam > 1 ) && victim != ch ) { if( victim.IsAffected( Affect.AFFECT_FIRESHIELD ) && !ch.IsAffected( Affect.AFFECT_FIRESHIELD ) ) InflictSpellDamage( victim, ch, dam / 2, "fireshield", AttackType.DamageType.fire ); if (victim.IsAffected( Affect.AFFECT_COLDSHIELD) && !ch.IsAffected(Affect.AFFECT_COLDSHIELD)) InflictSpellDamage( victim, ch, dam / 2, "coldshield", AttackType.DamageType.cold ); if (victim.IsAffected(Affect.AFFECT_SHOCK_SHIELD) && !ch.IsAffected(Affect.AFFECT_SHOCK_SHIELD)) InflictSpellDamage( victim, ch, dam / 2, "shockshield", AttackType.DamageType.electricity ); /* Soulshield is a complex one. If the attacker and victim are of * opposite alignment, the shield retaliates with 1/2 damage just like * any other shield. If the victim is neutral and the attacker is * not, the shield retaliates with 1/4 damage. If the victim is good * or evil and the attacker is neutral, the shield retaliates with * 1/8 damage. If the attacker and victim are of same alignment, * the shield does nothing. */ if (victim.IsAffected(Affect.AFFECT_SOULSHIELD) && !ch.IsAffected(Affect.AFFECT_SOULSHIELD)) { if( victim.IsEvil() && ch.IsGood() ) InflictSpellDamage(victim, ch, dam / 2, "soulshield", AttackType.DamageType.harm); else if( victim.IsGood() && ch.IsEvil() ) InflictSpellDamage(victim, ch, dam / 2, "soulshield", AttackType.DamageType.harm); else if( victim.IsNeutral() && ( ch.IsEvil() || ch.IsGood() ) ) InflictSpellDamage(victim, ch, dam / 4, "soulshield", AttackType.DamageType.harm); else if( victim.IsGood() && ch.IsNeutral() ) InflictSpellDamage(victim, ch, dam / 8, "soulshield", AttackType.DamageType.harm); else if( victim.IsEvil() && ch.IsNeutral() ) InflictSpellDamage(victim, ch, dam / 8, "soulshield", AttackType.DamageType.harm); } } if (victim.IsAffected( Affect.AFFECT_BERZERK ) && victim.CurrentPosition <= Position.stunned ) victim.RemoveAffect(Affect.AFFECT_BERZERK); if (dam > 0 && skill != "barehanded fighting" && IsWieldingPoisoned( ch, weapon ) && !Magic.SpellSavingThrow( ch.Level, victim, AttackType.DamageType.poison ) ) { InflictPoison( "poison_weapon", ch.Level, IsWieldingPoisoned( ch, weapon ), ch, victim ); SocketConnection.Act( "$n&n suffers from the &+Gpoison&n inflicted upon $m.", victim, null, null, SocketConnection.MessageTarget.room, true ); Object.StripAffect( Object.GetEquipmentOnCharacter( ch, weapon ), Affect.AffectType.skill, "poison weapon" ); } victim.UpdatePosition(); switch( victim.CurrentPosition ) { case Position.mortally_wounded: victim.SendText( "&+LYou are &+Rmo&n&+rr&+Rt&n&+ral&+Rl&n&+ry&+L wounded, and will die soon, if not aided.&n\r\n" ); SocketConnection.Act( "$n&+L is &+Rmo&n&+rr&+Rt&n&+ral&+Rl&n&+ry&+L wounded, and will die soon, if not aided.&n", victim, null, null, SocketConnection.MessageTarget.room, true ); StopNotVicious( victim ); break; case Position.incapacitated: victim.SendText( "&+LYou are incapacitated and will &n&+rbl&+Re&n&+re&+Rd&+L to death, if not aided.&n\r\n" ); SocketConnection.Act( "$n&+L is incapacitated and will slowly &n&+rbl&+Re&n&+re&+Rd&+L to death, if not aided.&n", victim, null, null, SocketConnection.MessageTarget.room, true ); StopNotVicious( victim ); break; case Position.stunned: victim.SendText( "&+LYou are stunned, but will probably recover.&n\r\n" ); SocketConnection.Act( "$n&+L is stunned, but will probably recover.&n", victim, null, null, SocketConnection.MessageTarget.room, true ); break; case Position.dead: if( victim == ch ) { victim.SendText( "&+LYou have been &+Rsl&n&+ra&+Ri&n&+rn&+L!&n\r\n\r\n" ); } else { string buf = String.Format( "&+LYou have been &+Rsl&n&+ra&+Ri&n&+rn&+L by&n {0}&+L!&n\r\n\r\n", ch.ShowNameTo( victim, false ) ); victim.SendText( buf ); } /* Added this to stop a bug. */ Combat.StopFighting( victim, true ); SocketConnection.Act( "$n&+L is &n&+rdead&+L!&n", victim, null, null, SocketConnection.MessageTarget.room, true ); break; default: if( dam > victim.GetMaxHit() / 5 ) victim.SendText( "That really did &+RHURT&n!\r\n" ); if( victim.Hitpoints < victim.GetMaxHit() / 10 ) victim.SendText( "You sure are &n&+rBL&+RE&n&+rE&+RDI&n&+rN&+RG&n!\r\n" ); break; } // Check for weapon procs if( ( obj = Object.GetEquipmentOnCharacter( ch, weapon ) ) && Position.dead != victim.CurrentPosition ) { if( obj.SpecFun.Count > 0 ) obj.CheckSpecialFunction(true); } /* * Sleep spells and extremely wounded folks. */ if( !victim.IsAwake() ) /* lets make NPC's not slaughter PC's */ { if( victim.Fighting && victim.Fighting.Hunting && victim.Fighting.Hunting.Who == victim ) StopHunting( victim.Fighting ); if( victim.Fighting && !victim.IsNPC() && ch.IsNPC() ) StopFighting( victim, true ); else StopFighting( victim, false ); } /* * Payoff for killing things. */ if( victim.CurrentPosition == Position.dead ) { // Done in attempt to squelch the combat continuation bug StopFighting( victim, true ); if( !victim.HasActionBit(MobTemplate.ACT_NOEXP ) || !victim.IsNPC() ) GroupExperienceGain( ch, victim ); if( ch.IsNPC() ) { if( ch.Hunting ) { if( ch.Hunting.Who == victim ) StopHunting( ch ); } if( ch.IsHating(victim) ) { ch.StopHating( victim ); } } if( !victim.IsNPC() ) { if( ch.IsNPC() ) { ( (PC)victim ).MobDeaths++; if( victim.IsGuild() ) { ( (PC)victim ).GuildMembership.MonsterDeaths++; ( (PC)victim ).GuildMembership.Score += CalculateDeathScore( ch, victim ); } ( (PC)victim ).Score += CalculateDeathScore( ch, victim ); } else { ( (PC)ch ).PlayerKills++; ( (PC)victim ).PlayerDeaths++; ( (PC)victim ).Score += CalculateDeathScore( ch, victim ); ( (PC)ch ).Score += CalculateKillScore( ch, victim ); if( ch.IsGuild() && victim.IsGuild() && ( (PC)ch ).GuildMembership != ( (PC)victim ).GuildMembership ) { ( (PC)ch ).GuildMembership.PlayerKills++; ( (PC)victim ).GuildMembership.PlayerDeaths++; ( (PC)ch ).GuildMembership.Score += CalculateKillScore( ch, victim ); ( (PC)victim ).GuildMembership.Score += CalculateDeathScore( ch, victim ); } } string logBuf = String.Format( "{0}&n killed by {1}&n at {2}", victim.Name, ( ch.IsNPC() ? ch.ShortDescription : ch.Name ), victim.InRoom.IndexNumber ); Log.Trace( logBuf ); ImmortalChat.SendImmortalChat( ch, ImmortalChat.IMMTALK_DEATHS, Limits.LEVEL_AVATAR, logBuf ); /* * Dying penalty: * * At level 1 you lose 12.5% of a level. * At level 50 you lose 25% of a level. */ // Made it so people level 5 and under lose no exp from death. if( ch.Level > 5 ) victim.GainExperience( ( 0 - ( ( ( 50 + victim.Level ) * ExperienceTable.Table[ victim.Level ].LevelExperience ) / 400 ) ) ); if( victim.Level < 2 && victim.ExperiencePoints < 1 ) victim.ExperiencePoints = 1; } else { if( !ch.IsNPC() ) { ( (PC)ch ).MobKills++; if( ch.IsGuild() ) { ( (PC)ch ).GuildMembership.MonsterKills++; ( (PC)ch ).GuildMembership.Score += CalculateKillScore( ch, victim ); } ( (PC)ch ).Score += CalculateKillScore( ch, victim ); } } KillingBlow( ch, victim ); return true; } if( victim == ch ) { return false; } /* * Wimp out? */ if( victim.IsNPC() && dam > 0 ) { if( ( victim.HasActionBit(MobTemplate.ACT_WIMPY ) && MUDMath.NumberBits( 1 ) == 0 && victim.Hitpoints < victim.GetMaxHit() / 5 ) || (victim.IsAffected( Affect.AFFECT_CHARM) && victim.Master && victim.Master.InRoom != victim.InRoom ) ) { StartFearing( victim, ch ); StopHunting( victim ); CommandType.Interpret(victim, "flee"); } } if( !victim.IsNPC() && victim.Hitpoints > 0 && victim.Hitpoints <= victim.Wimpy ) { CommandType.Interpret(victim, "flee"); } return false; }
/// <summary> /// Checks whether the victim is able to dodge the attacker's swing. /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <returns></returns> public static bool CheckDodge( CharData ch, CharData victim ) { if( !victim.IsAwake() || victim.CurrentPosition < Position.reclining ) return false; if (ch.IsAffected(Affect.AFFECT_DAZZLE)) return false; if( !victim.HasSkill( "dodge" ) ) return false; int chance = victim.GetSkillChance("dodge"); // Size difference bonus for dodge for halflings - they get 2% dodge // bonus per size difference between them and the attacker. -- Xangis // Drow get a flat 15% bonus. if( victim.GetRace() == Race.RACE_HALFLING ) { if( ch.CurrentSize > victim.CurrentSize ) { chance += 3 * ( ch.CurrentSize - victim.CurrentSize ); } } else if( victim.HasInnate( Race.RACE_GOOD_DODGE ) ) { chance += 8; } else if( victim.HasInnate( Race.RACE_BAD_DODGE ) ) { chance -= 3; } // Bashed mobs/creatures have a hard time dodging if( victim.CurrentPosition < Position.fighting ) { chance -= 25; } // Leap is 16% max at level 50. Considering crappy thri hitpoints it's necessary. if( victim.GetRace() == Race.RACE_THRIKREEN && MUDMath.NumberPercent() <= ( victim.Level / 3 ) ) { SocketConnection.Act( "$N&n leaps over your attack.", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "You leap over $n&n's attack.", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$N&n leaps over $n&n's attack.", ch, null, victim, SocketConnection.MessageTarget.room_vict ); return true; } victim.PracticeSkill( "dodge" ); if( MUDMath.NumberPercent() >= chance - ch.Level ) return false; switch( MUDMath.NumberRange( 1, 2 ) ) { case 1: SocketConnection.Act( "$N&n dodges your attack.", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "You dodge $n&n's attack.", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$N&n dodges $n&n's attack.", ch, null, victim, SocketConnection.MessageTarget.room_vict ); break; case 2: SocketConnection.Act( "$N&n sidesteps your attack.", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "You narrowly dodge $n&n's attack.", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$N&n avoids $n&n's attack.", ch, null, victim, SocketConnection.MessageTarget.room_vict ); break; default: break; } if( ch.Fighting == null ) SetFighting( ch, victim ); if( victim.Fighting == null ) SetFighting( victim, ch ); return true; }
/// <summary> /// Inflicts damage from a spell, based on the weapon damage() function, but customized for spells. /// /// Needs to be cleaned up because it's just too big (600+ lines). /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <param name="dam"></param> /// <param name="spell"></param> /// <param name="damType"></param> /// <returns></returns> public static bool InflictSpellDamage( CharData ch, CharData victim, int dam, Spell spell, AttackType.DamageType damType ) { if( ch == null || victim == null || victim.CurrentPosition == Position.dead ) return true; // Remove memorization and meditation bits. // And invis. ch.BreakInvisibility(); victim.BreakMeditate(); victim.BreakMemorization(); if( CheckShrug( ch, victim ) ) return false; if( victim.CurrentPosition == Position.sleeping && !( victim.GetRace() == Race.RACE_FIRE_ELE && damType == AttackType.DamageType.fire ) && !( victim.GetRace() == Race.RACE_WATER_ELE && damType == AttackType.DamageType.water ) && !( victim.GetRace() == Race.RACE_EARTH_ELE && damType == AttackType.DamageType.earth ) && !( victim.GetRace() == Race.RACE_AIR_ELE && damType == AttackType.DamageType.wind ) ) { SocketConnection.Act( "$n&n has a rude awakening!", victim, null, null, SocketConnection.MessageTarget.room ); victim.CurrentPosition = Position.resting; if( ch.InRoom == victim.InRoom && ch.FlightLevel == victim.FlightLevel ) SetFighting( victim, ch ); } // Check for globe spells. See also FinishSpell under TargetType.singleCharacterOffensive // This check here is just to prevent area effect spells from // doing damage if too low level. The check for direct spells is in // Magic.cs if (victim.IsAffected( Affect.AFFECT_MAJOR_GLOBE) && (spell.SpellCircle[(int)ch.CharacterClass.ClassNumber] <= 6 || spell.Name == "fireshield" || spell.Name == "shockshield" || spell.Name == "soulshield" || spell.Name == "coldshield" ) ) { SocketConnection.Act( "&+RThe globe around $N&+R's body bears the brunt of your assault!&n", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "&+RYour globe deflects $n&+R's attack!&n", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "&+R$N&+R's globe deflects $n&+R's attack!&n", ch, null, victim, SocketConnection.MessageTarget.room_vict ); return false; } if (victim.IsAffected( Affect.AFFECT_GREATER_SPIRIT_WARD) && spell.SpellCircle[(int)ch.CharacterClass.ClassNumber] <= 5) { SocketConnection.Act( "&+WThe aura around $N&+W's body bears the brunt of your assault!&n", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "&+WYour globe absorbs $n&+W's attack!&n", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "&+W$N&+W's aura absorbs $n&+W's attack!&n", ch, null, victim, SocketConnection.MessageTarget.room_vict ); return false; } if (victim.IsAffected( Affect.AFFECT_MINOR_GLOBE) && spell.SpellCircle[(int)ch.CharacterClass.ClassNumber] <= 4) { SocketConnection.Act( "&+RThe globe around $N&+R's body bears the brunt of your assault!&n", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "&+RYour globe deflects $n&+R's attack!&n", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "&+R$N&+R's globe deflects $n&+R's attack!&n", ch, null, victim, SocketConnection.MessageTarget.room_vict ); return false; } if (victim.IsAffected( Affect.AFFECT_SPIRIT_WARD) && spell.SpellCircle[(int)ch.CharacterClass.ClassNumber] <= 3) { SocketConnection.Act( "&+WThe aura around $N&+W's body bears the brunt of your assault!&n", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "&+WYour globe absorbs $n&+W's attack!&n", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "&+W$N&+W's aura absorbs $n&+W's attack!&n", ch, null, victim, SocketConnection.MessageTarget.victim ); return false; } /* * Stop up any residual loopholes. */ // 1275 is average damage from Akiaurn's Power Word // I changed this to reflect that. if( ( dam > 1275 ) && ch.Level < Limits.LEVEL_AVATAR && ch.GetRace() != Race.RACE_DRAGON ) { string buf3; if( ch.IsNPC() && ch.Socket ) buf3 = String.Format( "Spell_Damage: {0} from {1} by {2}: > 1275 points with {3} spell!", dam, ch.Name, ch.Socket.Original.Name, spell.Name ); else buf3 = String.Format( "Spell_Damage: {0} from {1}: > 1275 points with {2} spell!", dam, ch.IsNPC() ? ch.ShortDescription : ch.Name, spell.Name ); Log.Error( buf3, 0 ); dam = 1275; } if (victim.IsAffected( Affect.AFFECT_MINOR_PARA) && !( victim.GetRace() == Race.RACE_FIRE_ELE && damType == AttackType.DamageType.fire ) && !( victim.GetRace() == Race.RACE_WATER_ELE && damType == AttackType.DamageType.water ) && !( victim.GetRace() == Race.RACE_EARTH_ELE && damType == AttackType.DamageType.earth ) && !( victim.GetRace() == Race.RACE_AIR_ELE && damType == AttackType.DamageType.wind ) ) { SocketConnection.Act( "$n&n disrupts the magic preventing $N&n from moving.", ch, null, victim, SocketConnection.MessageTarget.room_vict ); SocketConnection.Act( "You disrupt the magic preventing $N&n from moving.", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "&+YYou can move again.&n", ch, null, victim, SocketConnection.MessageTarget.victim ); victim.RemoveAffect( Affect.AFFECT_MINOR_PARA ); victim.AffectStrip( Affect.AffectType.spell, "earthen grasp" ); victim.AffectStrip( Affect.AffectType.spell, "greater earthen grasp"); } bool immune = false; if( victim != ch ) { /* * Certain attacks are forbidden. * Most other attacks are returned. */ if( IsSafe( ch, victim ) ) return false; // is_safe could wipe out victim, as it calls procs if a boss // check and see that victim is still valid if( !victim ) return true; Crime.CheckAttemptedMurder( ch, victim ); if( victim.CurrentPosition > Position.stunned && !( victim.GetRace() == Race.RACE_FIRE_ELE && damType == AttackType.DamageType.fire ) && !( victim.GetRace() == Race.RACE_WATER_ELE && damType == AttackType.DamageType.water ) && !( victim.GetRace() == Race.RACE_EARTH_ELE && damType == AttackType.DamageType.earth ) && !( victim.GetRace() == Race.RACE_AIR_ELE && damType == AttackType.DamageType.wind ) ) { // Offensive spells engage victim if not fighting, and // caster only if neither are fighting. if( !victim.Fighting && victim.InRoom == ch.InRoom && victim.FlightLevel == ch.FlightLevel ) { SetFighting( victim, ch ); if( !ch.Fighting ) SetFighting( ch, victim ); // Can't have prone people automaticaly stand. if( victim.CurrentPosition == Position.standing ) victim.CurrentPosition = Position.fighting; } /* * If NPC victim is following, ch might attack victim's master. * No charm check here because charm would be dispelled from * tanking mobile when combat ensues thus ensuring PC charmer is * not harmed. * Check for is_same_group wont work as following mobile is not * always grouped with PC charmer */ if( ch.IsNPC() && victim.IsNPC() && victim.Master && victim.Master.InRoom == ch.InRoom && MUDMath.NumberBits( 2 ) == 0 ) { StopFighting( ch, false ); SetFighting( ch, victim.Master ); return false; } } /* * More charm stuff. */ if( victim.Master == ch && !( victim.GetRace() == Race.RACE_FIRE_ELE && damType == AttackType.DamageType.fire ) && !( victim.GetRace() == Race.RACE_WATER_ELE && damType == AttackType.DamageType.water ) && !( victim.GetRace() == Race.RACE_EARTH_ELE && damType == AttackType.DamageType.earth ) && !( victim.GetRace() == Race.RACE_AIR_ELE && damType == AttackType.DamageType.wind ) ) StopFighting( victim, true ); /* * Hunting stuff... */ if( dam != 0 && victim.IsNPC() && !( victim.GetRace() == Race.RACE_FIRE_ELE && damType == AttackType.DamageType.fire ) && !( victim.GetRace() == Race.RACE_WATER_ELE && damType == AttackType.DamageType.water ) && !( victim.GetRace() == Race.RACE_EARTH_ELE && damType == AttackType.DamageType.earth ) && !( victim.GetRace() == Race.RACE_AIR_ELE && damType == AttackType.DamageType.wind ) ) { StartGrudge( victim, ch, false ); } /* * Damage modifiers. */ if (victim.IsAffected( Affect.AFFECT_SANCTUARY)) dam /= 2; if ((victim.IsAffected( Affect.AFFECT_PROTECT_EVIL)) && ch.IsEvil()) dam -= dam / 8; else if ((victim.IsAffected( Affect.AFFECT_PROTECT_GOOD)) && ch.IsGood()) dam -= dam / 8; if( dam < 0 ) dam = 0; } switch( victim.CheckRIS( damType ) ) { case Race.ResistanceType.resistant: dam -= dam / 3; break; case Race.ResistanceType.immune: immune = true; dam = 0; break; case Race.ResistanceType.susceptible: dam += dam / 2; break; case Race.ResistanceType.vulnerable: dam *= 2; break; } if( ( damType == AttackType.DamageType.wind || damType == AttackType.DamageType.gas || damType == AttackType.DamageType.asphyxiation ) && victim.IsAffected( Affect.AFFECT_DENY_AIR)) { if( MUDMath.NumberPercent() < 50 ) { victim.SendText( "&+CYou deny the damage.&n\r\n" ); immune = true; dam = 0; } else dam -= dam / 5; } if (damType == AttackType.DamageType.fire && victim.IsAffected( Affect.AFFECT_DENY_FIRE)) { if( MUDMath.NumberPercent() < 50 ) { victim.SendText( "&+rYou deny the damage.&n\r\n" ); immune = true; dam = 0; } else dam -= dam / 5; } if( ( damType == AttackType.DamageType.earth || damType == AttackType.DamageType.crushing ) && victim.IsAffected(Affect.AFFECT_DENY_EARTH)) { if( MUDMath.NumberPercent() < 50 ) { victim.SendText( "&+yYou deny the damage.&n\r\n" ); immune = true; dam = 0; } else dam -= dam / 5; } if( ( damType == AttackType.DamageType.water || damType == AttackType.DamageType.acid || damType == AttackType.DamageType.drowning ) && victim.IsAffected(Affect.AFFECT_DENY_WATER)) { if( MUDMath.NumberPercent() < 50 ) { victim.SendText( "&+bYou deny the damage.&n\r\n" ); immune = true; dam = 0; } else dam -= dam / 5; } // Check for protection spells that give 25% damage reduction - Xangis if (damType == AttackType.DamageType.fire && victim.IsAffected( Affect.AFFECT_PROTECT_FIRE)) dam = ( dam * 3 ) / 4; else if (damType == AttackType.DamageType.cold && victim.IsAffected( Affect.AFFECT_PROTECT_COLD)) dam = ( dam * 3 ) / 4; else if (damType == AttackType.DamageType.acid && victim.IsAffected( Affect.AFFECT_PROTECT_ACID)) dam = ( dam * 3 ) / 4; else if (damType == AttackType.DamageType.gas && victim.IsAffected( Affect.AFFECT_PROTECT_GAS)) dam = ( dam * 3 ) / 4; else if (damType == AttackType.DamageType.electricity && victim.IsAffected( Affect.AFFECT_PROTECT_LIGHTNING)) dam = ( dam * 3 ) / 4; /* * We moved DamageMessage out of the victim != ch if above * so self damage would show. Other valid type_undefined * damage is ok to avoid like mortally wounded damage */ if( spell != Spell.SpellList["reserved"] && !( victim.GetRace() == Race.RACE_FIRE_ELE && damType == AttackType.DamageType.fire ) && !( victim.GetRace() == Race.RACE_WATER_ELE && damType == AttackType.DamageType.water ) && !( victim.GetRace() == Race.RACE_EARTH_ELE && damType == AttackType.DamageType.earth ) && !( victim.GetRace() == Race.RACE_AIR_ELE && damType == AttackType.DamageType.wind ) ) SendSpellDamageMessage( ch, victim, dam, spell, immune ); /* PC to PC damage quartered. * NPC to PC damage divided by 3. */ if( dam > 0 && !victim.IsNPC() && victim != ch ) { if( !ch.IsNPC() ) dam /= 4; else dam /= 3; } /* * Hurt the victim. * Inform the victim of his new state. */ if( !( victim.GetRace() == Race.RACE_FIRE_ELE && damType == AttackType.DamageType.fire ) && !( victim.GetRace() == Race.RACE_WATER_ELE && damType == AttackType.DamageType.water ) && !( victim.GetRace() == Race.RACE_EARTH_ELE && damType == AttackType.DamageType.earth ) && !( victim.GetRace() == Race.RACE_AIR_ELE && damType == AttackType.DamageType.wind ) ) { /* Added damage exp! */ // chance added because people level faster and faster as they get higher level... // you can now only get damage exp on mobs that con easy or better // and there's only a 25% chance per hit of you evern being eligible for damage exp. if( MUDMath.NumberPercent() < 25 && victim.Level >= ( ch.Level - 3 ) ) ch.GainExperience( Math.Max( 1, dam / 20 ) ); victim.Hitpoints -= dam; } else { string attack; if( spell != null && spell != Spell.SpellList["none"] ) attack = spell.Name; else attack = "it"; SocketConnection.Act( "$N&n absorbs your $t!", ch, attack, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "You absorb $n&n's $t!", ch, attack, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$N&n absorbs $n&n's $t", ch, attack, victim, SocketConnection.MessageTarget.room_vict ); if( ch.IsImmortal() ) { string buf4 = String.Format( "You healed {0} damage.", victim.GetMaxHit() >= dam + victim.Hitpoints ? dam : victim.GetMaxHit() - victim.Hitpoints ); ch.SendText( buf4 ); } victim.Hitpoints = Math.Min( victim.GetMaxHit(), victim.Hitpoints + dam ); return false; } if( !victim.IsNPC() && victim.Level >= Limits.LEVEL_AVATAR && victim.Hitpoints < 1 ) victim.Hitpoints = 1; if (victim.IsAffected(Affect.AFFECT_BERZERK) && victim.CurrentPosition <= Position.stunned ) victim.RemoveAffect(Affect.AFFECT_BERZERK); victim.UpdatePosition(); switch( victim.CurrentPosition ) { case Position.mortally_wounded: victim.SendText( "&+LYou are &+Rmo&n&+rr&+Rt&n&+ral&+Rl&n&+ry&+L wounded, and will die soon, if not aided.&n\r\n" ); SocketConnection.Act( "$n&+L is &+Rmo&n&+rr&+Rt&n&+ral&+Rl&n&+ry&+L wounded, and will die soon, if not aided.&n", victim, null, null, SocketConnection.MessageTarget.room, true ); StopNotVicious( victim ); break; case Position.incapacitated: victim.SendText( "&+LYou are incapacitated and will slowly &n&+rbl&+Re&n&+re&+Rd&+L to death, if not aided.\r\n" ); SocketConnection.Act( "$n&+L is incapacitated and will slowly &n&+rbl&+Re&n&+re&+Rd&+L to death, if not aided.&n", victim, null, null, SocketConnection.MessageTarget.room, true ); StopNotVicious( victim ); break; case Position.stunned: victim.SendText( "&+LYou are stunned, but will probably recover.&n\r\n" ); SocketConnection.Act( "$n&+L is stunned, but will probably recover.&n", victim, null, null, SocketConnection.MessageTarget.room, true ); break; case Position.dead: SocketConnection.Act( spell.MessageKill, ch, null, victim, SocketConnection.MessageTarget.room_vict ); SocketConnection.Act( spell.MessageKill, ch, null, victim, SocketConnection.MessageTarget.character ); if( victim == ch ) { victim.SendText( "&+LYou have been &+Rsl&n&+ra&+Ri&n&+rn&+L!&n\r\n\r\n" ); } else { string buf = String.Format( "&+LYou have been &+Rsl&n&+ra&+Ri&n&+rn&+L by&n {0}&+L!&n\r\n\r\n", ch.ShowNameTo( victim, false ) ); victim.SendText( buf ); } StopFighting( victim, true ); SocketConnection.Act( "$n&+L is &n&+rdead&+L!&n", victim, null, null, SocketConnection.MessageTarget.room, true ); break; default: if( dam > victim.GetMaxHit() / 5 ) victim.SendText( "That really did &+RHURT&n!\r\n" ); if( victim.Hitpoints < victim.GetMaxHit() / 10 ) victim.SendText( "You sure are &n&+rBL&+RE&n&+rE&+RDI&n&+rN&+RG&n!\r\n" ); break; } /* * Sleep spells and extremely wounded folks. */ if( !victim.IsAwake() ) /* lets make NPC's not slaughter PC's */ { if( victim.Fighting && victim.Fighting.Hunting && victim.Fighting.Hunting.Who == victim ) StopHunting( victim.Fighting ); if( victim.Fighting && !victim.IsNPC() && ch.IsNPC() ) StopFighting( victim, true ); else StopFighting( victim, false ); } /* * Payoff for killing things. */ if( victim.CurrentPosition == Position.dead ) { StopFighting( ch, false ); if( !victim.HasActionBit(MobTemplate.ACT_NOEXP ) || !victim.IsNPC() ) GroupExperienceGain( ch, victim ); if( !victim.IsNPC() ) { if( ch.IsNPC() ) { ( (PC)victim ).MobDeaths++; if( victim.IsGuild() ) { ( (PC)victim ).GuildMembership.MonsterDeaths++; ( (PC)victim ).GuildMembership.Score += CalculateDeathScore( ch, victim ); } ( (PC)victim ).Score += CalculateDeathScore( ch, victim ); } else { ( (PC)ch ).PlayerKills++; ( (PC)victim ).PlayerDeaths++; ( (PC)victim ).Score += CalculateDeathScore( ch, victim ); ( (PC)ch ).Score += CalculateKillScore( ch, victim ); if( ch.IsGuild() && victim.IsGuild() && ( (PC)ch ).GuildMembership != ( (PC)victim ).GuildMembership ) { ( (PC)ch ).GuildMembership.PlayerKills++; ( (PC)victim ).GuildMembership.PlayerDeaths++; ( (PC)ch ).GuildMembership.Score += CalculateKillScore( ch, victim ); ( (PC)victim ).GuildMembership.Score += CalculateDeathScore( ch, victim ); } } string logBuf = String.Format( "{0}&n killed by {1}&n at {2}", victim.Name, ( ch.IsNPC() ? ch.ShortDescription : ch.Name ), victim.InRoom.IndexNumber ); Log.Trace( logBuf ); ImmortalChat.SendImmortalChat( ch, ImmortalChat.IMMTALK_DEATHS, Limits.LEVEL_AVATAR, logBuf ); /* * Dying penalty: * 1/2 way back to previous 2 levels. */ // Newbies do not lose exp from death. if( ch.Level > 5 ) victim.GainExperience( ( 0 - ( ( ( 50 + victim.Level ) * ExperienceTable.Table[ victim.Level ].LevelExperience ) / 400 ) ) ); if( victim.Level < 2 && victim.ExperiencePoints < 1 ) victim.ExperiencePoints = 1; } else { if( !ch.IsNPC() ) { ( (PC)ch ).MobKills++; if( ch.IsGuild() ) { ( (PC)ch ).GuildMembership.MonsterKills++; ( (PC)ch ).GuildMembership.Score += CalculateKillScore( ch, victim ); } ( (PC)ch ).Score += CalculateKillScore( ch, victim ); } } KillingBlow( ch, victim ); // Keep in mind after this point the character is not in the // CharList, not in any room, and is at the menu. Don't do // anything that would cause a segmentation fault. if( ch.IsGuild() && victim.IsGuild() && ( (PC)ch ).GuildMembership != ( (PC)victim ).GuildMembership ) { ( (PC)ch ).GuildMembership.Score += 20; } return true; } if( victim == ch ) { return false; } /* * Wimp out? */ if( victim.IsNPC() && dam > 0 ) { if( ( victim.HasActionBit(MobTemplate.ACT_WIMPY ) && MUDMath.NumberBits( 1 ) == 0 && victim.Hitpoints < victim.GetMaxHit() / 5 ) || (victim.IsAffected(Affect.AFFECT_CHARM) && victim.Master && victim.Master.InRoom != victim.InRoom ) ) { StartFearing( victim, ch ); StopHunting( victim ); CommandType.Interpret(victim, "flee"); } } if( !victim.IsNPC() && victim.Hitpoints > 0 && victim.Hitpoints <= victim.Wimpy ) { CommandType.Interpret(victim, "flee"); } return false; }
/// <summary> /// Do one round of attacks for one character. /// Note: This is a round, not a single attack! /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <param name="skill"></param> /// <returns></returns> public static bool CombatRound(CharData ch, CharData victim, string skill) { Object wield; int chance = 0; if( ch == null ) { return false; } if( victim == null ) { return false; } /* No casting/being para'd and hitting at the same time. */ if( ( ch.IsAffected( Affect.AFFECT_CASTING ) ) || ch.IsAffected( Affect.AFFECT_MINOR_PARA ) || ch.IsAffected(Affect.AFFECT_HOLD)) { return false; } /* I don't know how a dead person can hit someone/be hit. */ if( victim.CurrentPosition == Position.dead || victim.Hitpoints < -10 ) { StopFighting( victim, true ); return true; } /* * Set the fighting fields now. */ if( victim.CurrentPosition > Position.stunned ) { if( !victim.Fighting ) SetFighting( victim, ch ); // Can't have bashed/prone people just automatically be standing. if( victim.CurrentPosition == Position.standing ) victim.CurrentPosition = Position.fighting; } // HORRIBLE HORRIBLE! We've got index numbers hard-coded. TODO: FIXME: BUG: Get rid of this! if( ch.IsNPC() && ch.MobileTemplate != null && ( ch.MobileTemplate.IndexNumber == 9316 || ch.MobileTemplate.IndexNumber == 9748 ) && MUDMath.NumberPercent() < 20 ) { CheckShout( ch, victim ); } ch.BreakInvisibility(); // Everyone gets at least one swing/breath in battle. // This handles breathing, roaring, etc. if (!CheckRaceSpecial(ch, victim, skill)) SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); // Thrikreen primary hand extra attack, only thri extra attack that is // given to non-warriors in addition to warriors if( ch.GetRace() == Race.RACE_THRIKREEN && MUDMath.NumberPercent() < ch.Level ) { if( ch.IsClass(CharClass.Names.warrior) ) { switch( MUDMath.NumberRange( 1, 4 ) ) { case 1: if( Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_one ) ) SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); break; case 2: if( Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_two ) ) SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_two); break; case 3: if( Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_three ) ) SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_three); break; case 4: if( Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_four ) ) SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_four); break; } } else { SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); if( MUDMath.NumberPercent() < ch.Level / 2 ) { SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); } } } // Don't hurt a corpse. if( victim.CurrentPosition == Position.dead || victim.Hitpoints < -10 ) { StopFighting( ch, false ); { return true; } } // For NPCs we assume they have max skill value for their level. // When checking combat skills we only prDescriptor.actice them on a successful // check in order to make them go up slower. If they go up too slow // we can always practice them before they check. chance = ch.GetAttackChance(2); if( MUDMath.NumberPercent() < chance ) { ch.PracticeSkill( "second attack" ); SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); if( ch.Fighting != victim ) { return false; } } // Check for Thri-Kreen arm #3 if( ch.GetRace() == Race.RACE_THRIKREEN && ( wield = Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_three ) ) ) { if( wield.HasWearFlag( ObjTemplate.WEARABLE_WIELD ) ) { if( ch.IsNPC() ) { if( !ch.HasSkill( "second attack" )) { chance = ch.Level / 5; // Up to 10% chance of third arm for psis and } // other miscellaneous thris else { chance = ((ch.Level - Skill.SkillList["second attack"].ClassAvailability[(int)ch.CharacterClass.ClassNumber]) * 2 + 25); } } else { if (((PC)ch).SkillAptitude.ContainsKey("second attack")) { chance = ((PC)ch).SkillAptitude["second attack"]; } else { chance = 0; } } if( chance > 95 ) chance = 95; if( MUDMath.NumberPercent() < chance ) { ch.PracticeSkill( "second attack" ); SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_three); if( ch.Fighting != victim ) { return false; } } } } chance = ch.GetAttackChance(3); if( MUDMath.NumberPercent() < chance ) { ch.PracticeSkill( "third attack" ); SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); if( ch.Fighting != victim ) { return false; } } chance = ch.GetAttackChance(4); if( MUDMath.NumberPercent() < chance ) { ch.PracticeSkill( "fourth attack" ); SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); if( ch.Fighting != victim ) { return false; } } // Check for dual wield. May want to allow a second swing when dual wielding. // We'll wait and see what combat looks like before we decide - Xangis wield = Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_two ); if( wield ) { if( wield.HasWearFlag( ObjTemplate.WEARABLE_WIELD ) ) { ch.PracticeSkill( "dual wield" ); if (ch.IsNPC()) { chance = ch.Level; } else { if (((PC)ch).SkillAptitude.ContainsKey("dual wield")) { chance = ((PC)ch).SkillAptitude["dual wield"] * 2 / 3; } else { chance = 0; } } chance += ch.IsClass(CharClass.Names.ranger) ? 10 : 0; if( MUDMath.NumberPercent() < chance ) { SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_two); } } if( ch.Fighting != victim ) { return false; } } // Check for fourth arm on thrikreen if( ch.GetRace() == Race.RACE_THRIKREEN && ( wield = Object.GetEquipmentOnCharacter( ch, ObjTemplate.WearLocation.hand_four ) ) ) { if( wield.HasWearFlag( ObjTemplate.WEARABLE_WIELD ) ) { ch.PracticeSkill( "dual wield" ); chance = ch.IsNPC() ? ( ch.Level * 3 / 2 + 20 ) : ( (PC)ch ).SkillAptitude[ "dual wield" ]; if( chance > 95 ) { chance = 95; } if( MUDMath.NumberPercent() < chance ) { SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_four); } } if( ch.Fighting != victim ) { return false; } } // Don't hurt a corpse. if( victim.CurrentPosition == Position.dead || victim.Hitpoints < -10 ) { StopFighting( ch, false ); return true; } return false; }
/// <summary> /// Test for special abilities based on an attacker's race. /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <param name="skill"></param> /// <returns></returns> static bool CheckRaceSpecial( CharData ch, CharData victim, string skill ) { if( !MUDString.StringsNotEqual( Race.RaceList[ ch.GetRace() ].Name, "Earth Elemental" ) ) { SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); if( MUDMath.NumberPercent() < 9 ) { Spell.SpellList["dust blast"].Invoke(ch, ch.Level, victim ); } else if( MUDMath.NumberPercent() < 6 ) { Spell.SpellList["pebble"].Invoke(ch, ch.Level, victim ); } else if( MUDMath.NumberPercent() < 3 ) { Spell.SpellList["dirt cloud"].Invoke(ch, ch.Level, victim); } return true; } if( !MUDString.StringsNotEqual( Race.RaceList[ ch.GetRace() ].Name, "Fire Elemental" ) ) { SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); if( MUDMath.NumberPercent() < 9 ) { Spell.SpellList["fire bolt"].Invoke(ch, ch.Level, victim ); } else if( MUDMath.NumberPercent() < 6 ) { Spell.SpellList["burning hands"].Invoke(ch, ch.Level, victim ); } else if( MUDMath.NumberPercent() < 3 ) { Spell.SpellList["spark"].Invoke(ch, ch.Level, victim); } return true; } if( !MUDString.StringsNotEqual( Race.RaceList[ ch.GetRace() ].Name, "Air Elemental" ) ) { SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); if( MUDMath.NumberPercent() < 9 ) { Spell.SpellList["ice bolt"].Invoke(ch, ch.Level, victim); } else if( MUDMath.NumberPercent() < 6 ) { Spell.SpellList["cutting breeze"].Invoke(ch, ch.Level, victim); } else if( MUDMath.NumberPercent() < 3 ) { Spell.SpellList["cutting breeze"].Invoke(ch, ch.Level, victim); } return true; } if( !MUDString.StringsNotEqual( Race.RaceList[ ch.GetRace() ].Name, "Water Elemental" ) ) { SingleAttack(ch, victim, skill, ObjTemplate.WearLocation.hand_one); if( MUDMath.NumberPercent() < 9 ) { Spell.SpellList["water bolt"].Invoke(ch, ch.Level, victim); } else if( MUDMath.NumberPercent() < 6 ) { Spell spell = Spell.SpellList["chill touch"]; if (spell != null) { spell.Invoke(ch, ch.Level, victim); } } else if( MUDMath.NumberPercent() < 3 ) { Spell.SpellList["water blast"].Invoke(ch, ch.Level, victim); } return true; } if( !MUDString.StringsNotEqual( Race.RaceList[ ch.GetRace() ].Name, "Dragon" ) ) { if( MUDMath.NumberPercent() < ch.Level ) { return true; } } return false; }
/// <summary> /// Create an instance of a mobile from the provided template. /// </summary> /// <param name="mobTemplate"></param> /// <returns></returns> public static CharData CreateMobile( MobTemplate mobTemplate ) { int count; if( !mobTemplate ) { Log.Error("CreateMobile: null MobTemplate.", 0); throw new NullReferenceException(); } CharData mob = new CharData(); mob.MobileTemplate = mobTemplate; mob.Followers = null; mob.Name = mobTemplate.PlayerName; mob.ShortDescription = mobTemplate.ShortDescription; mob.FullDescription = mobTemplate.FullDescription; mob.Description = mobTemplate.Description; mob.SpecialFunction = mobTemplate.SpecFun; mob.SpecialFunctionNames = mobTemplate.SpecFunNames; mob.CharacterClass = mobTemplate.CharacterClass; mob.Level = MUDMath.FuzzyNumber( mobTemplate.Level ); mob.ActionFlags = mobTemplate.ActionFlags; mob.CurrentPosition = mobTemplate.DefaultPosition; mob.ChatterBotName = mobTemplate.ChatterBotName; // TODO: Look up the chatter bot name and load a runtime bot into the variable. mob.ChatBot = null; for( count = 0; count < Limits.NUM_AFFECT_VECTORS; ++count ) { mob.AffectedBy[ count ] = mobTemplate.AffectedBy[ count ]; } mob.Alignment = mobTemplate.Alignment; mob.Gender = mobTemplate.Gender; mob.SetPermRace( mobTemplate.Race ); mob.CurrentSize = Race.RaceList[ mob.GetRace() ].DefaultSize; if (mob.HasActionBit(MobTemplate.ACT_SIZEMINUS)) mob.CurrentSize--; if (mob.HasActionBit(MobTemplate.ACT_SIZEPLUS)) mob.CurrentSize++; mob.CastingSpell = 0; mob.CastingTime = 0; mob.PermStrength = MUDMath.Dice( 2, 46 ) + 8; mob.PermIntelligence = MUDMath.Dice( 2, 46 ) + 8; mob.PermWisdom = MUDMath.Dice( 2, 46 ) + 8; mob.PermDexterity = MUDMath.Dice( 2, 46 ) + 8; mob.PermConstitution = MUDMath.Dice( 2, 46 ) + 7; mob.PermAgility = MUDMath.Dice( 2, 46 ) + 8; mob.PermCharisma = MUDMath.Dice( 2, 46 ) + 8; mob.PermPower = MUDMath.Dice( 2, 46 ) + 8; mob.PermLuck = MUDMath.Dice( 2, 46 ) + 8; mob.ModifiedStrength = 0; mob.ModifiedIntelligence = 0; mob.ModifiedWisdom = 0; mob.ModifiedDexterity = 0; mob.ModifiedConstitution = 0; mob.ModifiedAgility = 0; mob.ModifiedCharisma = 0; mob.ModifiedPower = 0; mob.ModifiedLuck = 0; mob.Resistant = mobTemplate.Resistant; mob.Immune = mobTemplate.Immune; mob.Susceptible = mobTemplate.Susceptible; mob.Vulnerable = mobTemplate.Vulnerable; mob.MaxMana = mob.Level * 10; if( Race.RaceList[mobTemplate.Race].Coins ) { int level = mobTemplate.Level; mob.ReceiveCopper( MUDMath.Dice( 12, level ) / 32 ); mob.ReceiveSilver( MUDMath.Dice( 9, level ) / 32 ); mob.ReceiveGold( MUDMath.Dice( 5, level ) / 32 ); mob.ReceivePlatinum( MUDMath.Dice( 2, level ) / 32 ); } else { mob.SetCoins( 0, 0, 0, 0 ); } mob.ArmorPoints = MUDMath.Interpolate( mob.Level, 100, -100 ); // * MOB HITPOINTS * // // Was level d 8, upped it to level d 13 // considering mobs *still* won't have as many hitpoints as some players until // at least level 10, this shouldn't be too big an upgrade. // // Mob hitpoints are not based on constitution *unless* they have a // constitution modifier from an item, spell, or other affect // In light of recent player dissatisfaction with the // mob hitpoints, I'm implementing a log curve, using // hp = exp( 2.15135 + level*0.151231) // This will will result in the following hp matrix: // Level Hitpoints // 20 175 // 30 803 // 40 3643 // 50 16528 // 55 35207 // 60 75000 mob.MaxHitpoints = MUDMath.Dice( mob.Level, 13 ) + 1; // Mob hps are non-linear above level 10. if( mob.Level > 20 ) { int upper = (int)Math.Exp( 1.85 + mob.Level * 0.151231 ); int lower = (int)Math.Exp( 1.80 + mob.Level * 0.151231 ); mob.MaxHitpoints += MUDMath.NumberRange( lower, upper ); } else if (mob.Level > 10) { mob.MaxHitpoints += MUDMath.NumberRange(mob.Level * 2, ((mob.Level - 8) ^ 2 * mob.Level) / 2); } // Demons/devils/dragons gain an extra 30 hitpoints per level (+1500 at lvl 50). if (mob.GetRace() == Race.RACE_DEMON || mob.GetRace() == Race.RACE_DEVIL || mob.GetRace() == Race.RACE_DRAGON) { mob.MaxHitpoints += (mob.Level * 30); } mob.Hitpoints = mob.GetMaxHit(); // Horses get more moves, necessary for mounts. if(Race.RaceList[ mob.GetRace() ].Name.Equals( "Horse", StringComparison.CurrentCultureIgnoreCase )) { mob.MaxMoves = 290 + MUDMath.Dice( 4, 5 ); mob.CurrentMoves = mob.MaxMoves; } mob.LoadRoomIndexNumber = 0; // Insert in list. CharList.Add( mob ); // Increment count of in-game instances of mob. mobTemplate.NumActive++; return mob; }
/* * Compute xp for a kill. * Also adjust alignment of killer. * Edit this function to change xp computations. */ static int ComputeExperience( CharData killer, CharData victim ) { int percent = 100; int sign; int alignDir; // Uses a semi-exponential table called ExperienceTable.Table // no exp is awarded for anything 20 levels below you. // exp is reduced by 10% per level of the creature below you // for the first 9 levels, and then another 1% for the next 9 levels. // It actually counts down starting at 91%. /* If victim is lower level */ if( victim.Level < killer.Level ) { /* If victim is less than 10 levels below */ if( killer.Level - victim.Level < 10 ) { percent = 101 - ( ( killer.Level - victim.Level ) * 10 ); } /* If victim is less than 20 levels below */ else if( killer.Level - victim.Level < 20 ) { percent = 20 - ( killer.Level - victim.Level ); } else { percent = 0; } } /* If victim is over 10 levels over */ else if( victim.Level > ( killer.Level + 10 ) && killer.Level <= 20 ) { // Experience penalty for killing stuff way higher than you, 33 level difference and you // get about nothing. // Tweaked this slightly, 96 % at 10 levels above, 96% at 10 levels, etc percent = 129 - ( ( victim.Level - killer.Level ) * 4 ); if( percent < 2 ) percent = 2; } else percent += ( victim.Level - killer.Level ); if( killer.Alignment > 0 ) sign = 1; else sign = -1; if( victim.Alignment > 0 ) alignDir = -1; else alignDir = 1; int chance = Math.Abs( killer.Alignment - victim.Alignment ) - sign * killer.Alignment; chance /= 10; if( chance < 0 ) chance *= -1; if( killer.Level > victim.Level ) chance -= ( killer.Level - victim.Level ); chance = Macros.Range( 0, chance, 100 ); string lbuf = String.Format( "ComputeExperience: {0} has a {1} chance of gaining {2} align.", killer.Name, chance, alignDir ); ImmortalChat.SendImmortalChat( null, ImmortalChat.IMMTALK_SPAM, 0, lbuf ); if( MUDMath.NumberPercent() < chance && killer.Alignment != -1000 ) { killer.Alignment += alignDir; if( killer.Alignment <= -1000 ) killer.SendText( "&+RThe d&+rarkside t&+lakes over...&n\r\n" ); } killer.Alignment = Macros.Range( -1000, killer.Alignment, 1000 ); // 25% bonus for sanctuary if (victim.IsAffected(Affect.AFFECT_SANCTUARY)) { percent = ( percent * 5 ) / 4; } // 10% bonus for fireshield if( victim.IsAffected( Affect.AFFECT_FIRESHIELD ) ) { percent = ( percent * 11 ) / 10; } // 12.5% bonus for an armed opponent Object obj = Object.GetEquipmentOnCharacter( victim, ObjTemplate.WearLocation.hand_one ); if( obj ) { percent = ( percent * 9 ) / 8; } // 8.25% bonus for secondary weapon obj = Object.GetEquipmentOnCharacter( victim, ObjTemplate.WearLocation.hand_two ); if( obj ) { percent = ( percent * 13 ) / 12; } // 10% bonus for racially hated mobs if( MUDString.NameContainedIn( Race.RaceList[ victim.GetRace() ].Name, Race.RaceList[ killer.GetOrigRace() ].Hate ) ) { percent = ( percent * 11 ) / 10; } // 10% penalty for killing same race if( victim.GetRace() == killer.GetOrigRace() ) { percent = ( percent * 9 ) / 10; } // Lowbie experience bonus was eliminated since the ExperienceTable.Table // made it no longer necessary if( victim.IsNPC() ) { // 5% bonus for aggros if( victim.HasActionBit(MobTemplate.ACT_AGGRESSIVE ) ) { percent = ( percent * 21 ) / 20; } // 50% penalty for killing shopkeepers if( victim.MobileTemplate.ShopData != null ) percent = percent / 2; // No bonus for special function #1 because they get that by having a class. // 10% bonus for each extra special function. int count = victim.SpecialFunction.Count; percent = ( percent * ( 10 + count ) ) / 10; } else { // Player-vs-player experience // // Killing a level 1-5 makes you lose a small amount of experience // and you get no experience for anyone under level 10. // // Those 11-20 are worth one fifth experience. if( victim.Level < 6 ) { killer.SendText( "You killed a newbie! You feel like a twink!\r\n" ); return (victim.Level - 6); } if( victim.Level < 11 ) { return 0; } if( victim.Level < 20 ) { percent /= 5; } else { percent *= 2; } if( !killer.IsRacewar( victim ) ) { if( !killer.HasActionBit( PC.PLAYER_BOTTING )) { killer.SendText("You gain no experience for killing your own side.\r\n"); return 0; } // Same-side bot kills are worth normal experience. killer.SendText("You gain experience for vanquishing an automaton.\r\n"); } else if( killer.HasActionBit( PC.PLAYER_BOTTING )) { // Racewar bot kills. 50% bonus exp. killer.SendText("You gain bonus experience for killing an automaton.\r\n"); percent = percent * 3/2; } } int xp = ( percent * ExperienceTable.Table[ victim.Level ].MobExperience ) / 100; xp = Math.Max( 0, xp ); return xp; }
/// <summary> /// Try to find hidden things. /// </summary> /// <param name="ch"></param> /// <param name="str"></param> public static void Search(CharData ch, string[] str) { if( ch == null ) return; Room toRoom; Exit reverseExit; Exit exit = null; Object obj; bool isArg; Exit.Direction door; if (ch.InRoom == null) { ch.SendText("There's no searching to be done here.\r\n"); return; } /* Calculate the probability that ch finds something. */ int chance = ch.GetCurrInt() * 35 / 100; chance += ch.GetCurrWis() * 35 / 100; chance += ch.GetCurrLuck() * 10 / 100; // Dwarves, being natural architects, can search better - Xangis if (ch.GetRace() == Race.RACE_DWARF) chance += 25; if (chance > 95) chance = 95; if (chance < 10) chance = 10; /* Searching with arguments. */ if (str.Length != 0) { isArg = true; obj = ch.GetObjHere(str[0]); if (obj == null) { ch.SendText("You don't see that object here.\r\n"); return; } switch (obj.ItemType) { case ObjTemplate.ObjectType.quiver: case ObjTemplate.ObjectType.container: case ObjTemplate.ObjectType.npc_corpse: case ObjTemplate.ObjectType.pc_corpse: if (Macros.IsSet(obj.Values[1], ObjTemplate.CONTAINER_CLOSED.Vector)) { SocketConnection.Act("$p&n is closed.", ch, obj, null, SocketConnection.MessageTarget.character); return; } break; default: ch.SendText("That is not a container!\r\n"); return; } } else { isArg = false; obj = null; } /* Lag ch from searching. */ if (ch.Level < Limits.LEVEL_AVATAR) { ch.WaitState(MUDMath.FuzzyNumber(18)); } List<Object> list; if (obj != null) { list = obj.Contains; } else { list = ch.InRoom.Contents; } foreach (Object obj2 in list) { if (obj2.HasFlag(ObjTemplate.ITEM_SECRET) && chance > MUDMath.NumberPercent() && obj2.FlyLevel == ch.FlightLevel) { obj2.RemoveFlag(ObjTemplate.ITEM_SECRET); SocketConnection.Act("You find $p&n.", ch, obj2, null, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n points out $p&n!", ch, obj, null, SocketConnection.MessageTarget.room); return; } } /* Look for a hidden exit. */ door = Exit.Direction.invalid; for (int doornum = 0; doornum <= Limits.MAX_DIRECTION; doornum++) { /* If there's a secret exit that leads to a room. */ exit = ch.InRoom.GetExit((Exit.Direction)doornum); if (exit && exit.HasFlag(Exit.ExitFlag.secret) && exit.TargetRoom) { door = (Exit.Direction)doornum; break; } } if (isArg) { door = Exit.Direction.invalid; } if (door != Exit.Direction.invalid) { if (exit == null) { Log.Error("Command.Search: null exit found", 0); return; } if (exit.TargetRoom == null) { Log.Error("Command.Search: exit to null room found", 0); return; } if (MUDMath.NumberPercent() < chance) { if (ch.FlightLevel > 0) { ch.SendText("You're too high to be sure of what you saw.\r\n"); return; } ch.SendText("You found a hidden exit!\r\n"); SocketConnection.Act("$n&n points out a secret exit!", ch, null, null, SocketConnection.MessageTarget.room); /* Search out one side. */ exit.RemoveFlag(Exit.ExitFlag.secret); /* And the other side, if it exists. */ toRoom = Room.GetRoom(exit.IndexNumber); if (toRoom && (reverseExit = toRoom.GetExit(Exit.ReverseDirection(door))) && (reverseExit.TargetRoom == ch.InRoom)) { reverseExit.RemoveFlag(Exit.ExitFlag.secret); } return; } } if (!isArg) { // Chance of finding people hiding in the room foreach (CharData roomChar in ch.InRoom.People) { if (!roomChar.IsAffected(Affect.AFFECT_HIDE) || roomChar.FlightLevel != ch.FlightLevel) continue; if (MUDMath.NumberPercent() < chance) { roomChar.RemoveAffect(Affect.AFFECT_HIDE); /* People without DI can't search out invis hiding pplz. */ if (CharData.CanSee(ch, roomChar)) { SocketConnection.Act("$n&n points out $N&n lurking here!", ch, null, roomChar, SocketConnection.MessageTarget.room); SocketConnection.Act("You point out $N&n lurking here!", ch, null, roomChar, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n reveals your hiding spot!", ch, null, roomChar, SocketConnection.MessageTarget.victim); return; } roomChar.SetAffectBit(Affect.AFFECT_HIDE); } } } ch.SendText("You find nothing new.\r\n"); return; }
/// <summary> /// Turns a character into a corpse. /// /// TODO: Add a corpse to the corpse list when a corpse is created. /// TODO: Also remove corpses from the corpse list when a corpse decays. /// </summary> /// <param name="ch"></param> static void MakeCorpse( CharData ch ) { Object corpse; string name; int corpseIndexNumber; int timer; int level; // Need a bailout here if undead corpses are supposed to disintegrate. if( LeavesNoCorpse( ch ) ) { return; } // Different corpse settings for players and mobs - Xangis if( ch.IsNPC() ) { corpseIndexNumber = StaticObjects.OBJECT_NUMBER_CORPSE_NPC; name = ch.ShortDescription; timer = MUDMath.NumberRange( 15, 30 ); level = ch.Level; // Corpse level } else { corpseIndexNumber = StaticObjects.OBJECT_NUMBER_CORPSE_PC; name = ch.Name; timer = MUDMath.NumberRange( 180, 240 ) + ( ch.Level * 2 ); level = ch.Level; // Corpse level } /* * This longwinded corpse creation routine comes about because * we dont want anything created AFTER a corpse to be placed * INSIDE a corpse. This had caused crashes from ObjectUpdate() * in Object.Extract(). */ if( ch.GetCash() > 0 ) { Object coins = Object.CreateMoney( ch.GetCopper(), ch.GetSilver(), ch.GetGold(), ch.GetPlatinum() ); corpse = Database.CreateObject( Database.GetObjTemplate( corpseIndexNumber ), 0 ); corpse.AddToObject( coins ); ch.SetCoins( 0, 0, 0, 0 ); } else { corpse = Database.CreateObject( Database.GetObjTemplate( corpseIndexNumber ), 0 ); } corpse.Timer = timer; corpse.Values[ 0 ] = (int)Race.RaceList[ ch.GetRace() ].BodyParts; corpse.Level = level; // corpse level string buf = String.Format( "corpse of {0}", name ); corpse.Name = buf; buf = String.Format( corpse.ShortDescription, name ); corpse.ShortDescription = buf; buf = String.Format( corpse.FullDescription, name ); corpse.FullDescription = buf; Object obj; for(int i = (ch.Carrying.Count - 1); i >= 0; i-- ) { obj = ch.Carrying[i]; // Remove items flagged inventory-only from all corpses. if (obj.HasFlag(ObjTemplate.ITEM_INVENTORY)) { Log.Trace("Removing inventory-item " + obj + " from character before creating corpse: " + corpse); obj.RemoveFromWorld(); } else { if (ch.IsNPC() && ch.MobileTemplate.ShopData && obj.WearLocation == ObjTemplate.WearLocation.none) { obj.RemoveFromChar(); obj.RemoveFromWorld(); } else { obj.RemoveFromChar(); corpse.AddToObject(obj); } } } corpse.AddToRoom( ch.InRoom ); if( !corpse.InRoom ) { Log.Error( "MakeCorpse: corpse " + corpse.ToString() + " sent to null room, deleting.", 0 ); corpse.RemoveFromWorld(); } return; }
/// <summary> /// Sign language for Drow only. /// </summary> /// <param name="ch"></param> /// <param name="str"></param> public static void Sign(CharData ch, string[] str) { if( ch == null ) return; if (str.Length == 0) { ch.SendText("Sign what to who?\r\n"); return; } if (ch.GetRace() != Race.RACE_DROW && !ch.IsImmortal()) { ch.SendText("You wiggle your fingers around.\r\n"); SocketConnection.Act("$n wiggles $s fingers around, looking foolish.", ch, null, null, SocketConnection.MessageTarget.room); return; } string text = String.Join(" ", str, 1, (str.Length - 1)); // We don't want to let them know they're drunk. SocketConnection.Act("You sign '&n$T&n'", ch, null, text, SocketConnection.MessageTarget.character); text = DrunkSpeech.MakeDrunk(text, ch); foreach (CharData roomChar in ch.InRoom.People) { if (roomChar == ch || roomChar.IsNPC()) continue; string text2; if (roomChar.IsImmortal() || roomChar.GetOrigRace() == Race.RACE_DROW) { text2 = String.Format("{0} signs '&n$T&n'", ch.ShowNameTo(roomChar, true)); } else { text2 = String.Format("{0} wiggles {1} fingers around.", ch.ShowNameTo(roomChar, true), ch.Gender == MobTemplate.Sex.male ? "his" : ch.Gender == MobTemplate.Sex.female ? "her" : "its"); } SocketConnection.Act(text, roomChar, null, text2, SocketConnection.MessageTarget.character); } // ch.door_trigger( argument ); return; }
/// <summary> /// Sends a damage message to a player. /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <param name="damage"></param> /// <param name="skill"></param> /// <param name="weapon"></param> /// <param name="immune"></param> static void SendDamageMessage( CharData ch, CharData victim, int damage, string skill, ObjTemplate.WearLocation weapon, bool immune ) { if( victim.CurrentPosition == Position.sleeping ) { SocketConnection.Act( "$n&n has a rude awakening!", victim, null, null, SocketConnection.MessageTarget.room ); victim.CurrentPosition = Position.resting; SetFighting( victim, ch ); } if (skill == "bash") { SocketConnection.Act( "You send $N&n crashing to the &n&+yground&n with your powerful bash.", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "$n&n's powerful bash sends you sprawling!", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$n&n sends $N&n sprawling with a powerful bash!", ch, null, victim, SocketConnection.MessageTarget.room_vict ); return; } if (skill == "headbutt") { if( ch != victim && damage > victim.Hitpoints + 10 ) { // a killing blow needs some nice verbage SocketConnection.Act( "You swiftly split $N&n's skull with your forehead, sending &+Rblood&n&+r and brains&n flying!", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "$n&n rears back and splits $N&n's skull with $s forehead!", ch, null, victim, SocketConnection.MessageTarget.everyone_but_victim ); SocketConnection.Act( "&+RBlood&n&+r and brains&n splatter everywhere as $N&n goes limp and collapses.", ch, null, victim, SocketConnection.MessageTarget.everyone_but_victim ); SocketConnection.Act( "$n&n's forehead smashes into you, crushing your skull and bringing on a wave of &+Lblackness&n.", ch, null, victim, SocketConnection.MessageTarget.victim ); //deal some decent damage } else if( ch != victim && damage > 100 ) { // a pretty nasty headbutt SocketConnection.Act( "Your headbutt smashes into $N&n's skull.", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "$n&n's headbutt smashes into $N&n's skull.", ch, null, victim, SocketConnection.MessageTarget.everyone_but_victim ); SocketConnection.Act( "$n&n's head smashes into you, sending you reeling.", ch, null, victim, SocketConnection.MessageTarget.victim ); //deal some decent damage } else if( ch != victim ) { SocketConnection.Act( "$n&n's headbutt leaves a red welt on $N&n's forehead.", ch, null, victim, SocketConnection.MessageTarget.everyone_but_victim ); SocketConnection.Act( "Your headbutt leaves a red welt on $N&n's forehead.", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "$n&n's headbutt leaves a red welt on your forehead.", ch, null, victim, SocketConnection.MessageTarget.victim ); } /* left the damage to self messages in the Command.Headbutt() function * because they are a little too tricky to implement through * this function */ return; } if (skill == "bodyslam" && ch != victim) { SocketConnection.Act( "You bodyslam $N&n!", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "$n&n bodyslams you!\r\nYou are stunned!", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$n&n bodyslams $N&n.", ch, null, victim, SocketConnection.MessageTarget.room_vict ); } if (skill == "instant kill") { switch( MUDMath.NumberRange( 1, 3 ) ) { case 1: case 2: SocketConnection.Act( "You place your weapon in the back of $N&n, resulting in some strange noises, some blood, and a corpse!", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "You realize you should have kept your vital organs somewhere safe as $n&n stabs you to death.", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$n&n places $s weapon in the back of $N&n, resulting in some strange noises, some blood, and a corpse!", ch, null, victim, SocketConnection.MessageTarget.room_vict ); break; case 3: SocketConnection.Act( "You place your weapon in the back of $N&n with such force that it comes out the other side!", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "$n&n ends your life with a well-placed backstab.", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$n&n places $s weapon in the back of $N&n with such force that it comes out the other side!", ch, null, victim, SocketConnection.MessageTarget.room_vict ); break; } return; } // Multiple backstab messages, feel free to add more. if (skill == "backstab" && damage > 0) { switch( MUDMath.NumberRange( 1, 4 ) ) { case 1: SocketConnection.Act( "$N&n howls in agony as you pierce $S backbone!", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "You howl in agony as you feel &+rpain&n in your back!", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$N&N howls in agony as $n&n pierces $S backbone!", ch, null, victim, SocketConnection.MessageTarget.room_vict ); break; case 2: SocketConnection.Act( "You place your $p&n silently and skillfully through the spine of $N&n.", ch, Object.GetEquipmentOnCharacter( ch, weapon ), victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "Your spine feels $p&n neatly slicing through it.", ch, Object.GetEquipmentOnCharacter( ch, weapon ), victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$n&n places $s $p&n into $N&n's back!", ch, Object.GetEquipmentOnCharacter( ch, weapon ), victim, SocketConnection.MessageTarget.room_vict ); break; case 3: SocketConnection.Act( "Blood flies everywhere as you stab $N&n in the back!", ch, null, victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "You feel a sharp stabbing sensation in your back!", ch, null, victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "Blood flies everywhere as $n&n places $s $p&n into $N&n's back!", ch, Object.GetEquipmentOnCharacter( ch, weapon ), victim, SocketConnection.MessageTarget.room_vict ); break; case 4: SocketConnection.Act( "You smile with perverse pleasure as your $p&n plunges through $N&n's soft tissue, piercing vital organs.", ch, Object.GetEquipmentOnCharacter( ch, weapon ), victim, SocketConnection.MessageTarget.character ); SocketConnection.Act( "$n&n smiles with perverse pleasure as $s $p&n plunges through $N&n's soft tissue, piercing vital organs.", ch, Object.GetEquipmentOnCharacter( ch, weapon ), victim, SocketConnection.MessageTarget.victim ); SocketConnection.Act( "$n&n smile with perverse pleasure as $s $p&n plunges through $N&n's soft tissue, piercing vital organs.", ch, Object.GetEquipmentOnCharacter( ch, weapon ), victim, SocketConnection.MessageTarget.room_vict ); break; } return; } if (skill == "poison weapon" || skill == "poison" || skill == "poison bite") return; string vp1; string attack; string buf1; string buf2; string buf3; string buf4; string buf5; // Adjectives based on amount of damage done. string adjective = String.Empty; if( damage > 100 ) { adjective = " godly"; } else if( damage > 75 ) { adjective = " devastating"; } else if( damage > 55 ) { adjective = " mighty"; } else if( damage > 40 ) { adjective = " awesome"; } else if( damage > 25 ) { adjective = " powerful"; } else if( damage > 4 ) { adjective = String.Empty; } // no message modifier for normal hits else if( damage > 2 ) { adjective = " mediocre"; } else if( damage > 0 ) { adjective = " feeble"; } string vp2 = String.Empty; if( damage == 0 ) { vp1 = "misses"; vp2 = String.Empty; } else { damage *= 100; if( victim.Hitpoints > 0 ) damage /= victim.Hitpoints; if( damage <= 1 ) { vp1 = "scratches"; } else if( damage <= 2 ) { vp1 = "grazes"; } else if( damage <= 3 ) { vp1 = "hits"; } else if( damage <= 4 ) { vp1 = "hits"; vp2 = " hard"; } else if( damage <= 5 ) { vp1 = "hits"; vp2 = " very hard"; } else if( damage <= 10 ) { vp1 = "mauls"; } else if( damage <= 15 ) { vp1 = "decimates"; } else if( damage <= 20 ) { vp1 = "makes"; vp2 = " stagger in pain"; } else if( damage <= 25 ) { vp1 = "maims"; } else if( damage <= 30 ) { vp1 = "mutilates"; } else if( damage <= 40 ) { vp1 = "disembowels"; } else if( damage <= 50 ) { vp1 = "eviscerates"; } else if( damage <= 75 ) { vp1 = "enshrouds"; vp2 = " in a mist of blood"; } else { vp1 = "beats the crap out of"; } } string punct = ( damage <= 40 ) ? "." : "!"; if (skill == "barehanded fighting") { if( ch.GetRace() >= Race.RaceList.Length ) { Log.Error( "SendDamageMessage: {0} invalid race", ch.GetRace() ); ch.SetPermRace( 0 ); } attack = Race.RaceList[ ch.GetRace() ].DamageMessage; buf1 = String.Format( "Your{0} {1} {2} $N&n{3}{4}", adjective, attack, vp1, vp2, punct ); buf2 = String.Format("$n&n's{0} {1} {2} you{3}{4}", adjective, attack, vp1, vp2, punct); buf3 = String.Format("$n&n's{0} {1} {2} $N&n{3}{4}", adjective, attack, vp1, vp2, punct); buf4 = String.Format("You{0} {1} {2} yourself{3}{4}", adjective, attack, vp1, vp2, punct); buf5 = String.Format("$n&n's{0} {1} {2} $m{3}{4}", adjective, attack, vp1, vp2, punct); } else { if (!String.IsNullOrEmpty(skill)) attack = Skill.SkillList[skill].DamageText; else { string buf = String.Format( "SendDamageMessage: bad damage type {0} for {1} damage caused by {2} to {3} with weapon {4}.", skill, damage, ch.Name, victim.Name, weapon ); Log.Error( buf, 0 ); skill = "barehanded fighting"; attack = AttackType.Table[ 0 ].Name; } if( immune ) { buf1 = String.Format("$N&n seems unaffected by your {0}!", attack); buf2 = String.Format("$n&n's {0} seems powerless against you.", attack); buf3 = String.Format("$N&n seems unaffected by $n&n's {0}!", attack); buf4 = String.Format("Luckily, you seem immune to {0}.", attack); buf5 = String.Format("$n&n seems unaffected by $s own {0}.", attack); } else { if (skill != "barehanded fighting" && IsWieldingPoisoned(ch, weapon)) { buf1 = String.Format("Your poisoned {0} {1} $N&n{2}{3}", attack, vp1, vp2, punct); buf2 = String.Format("$n&n's poisoned {0} {1} you{2}{3}", attack, vp1, vp2, punct); buf3 = String.Format("$n&n's poisoned {0} {1} $N&n{2}{3}", attack, vp1, vp2, punct); buf4 = String.Format("Your poisoned {0} {1} you{2}{3}", attack, vp1, vp2, punct); buf5 = String.Format("$n&n's poisoned {0} {1} $m{2}{3}", attack, vp1, vp2, punct); } else { buf1 = String.Format("Your{0} {1} {2} $N&n{3}{4}", adjective, attack, vp1, vp2, punct); buf2 = String.Format("$n&n's{0} {1} {2} you{3}{4}", adjective, attack, vp1, vp2, punct); buf3 = String.Format("$n&n's{0} {1} {2} $N&n{3}{4}", adjective, attack, vp1, vp2, punct); buf4 = String.Format("You{0} {1} {2} yourself{3}{4}", adjective, attack, vp1, vp2, punct); buf5 = String.Format("$n&n's{0} {1} {2} $m{3}{4}", adjective, attack, vp1, vp2, punct); } } } if( victim != ch ) { SocketConnection.Act( buf1, ch, null, victim, SocketConnection.MessageTarget.character, true ); SocketConnection.Act( buf2, ch, null, victim, SocketConnection.MessageTarget.victim, true ); SocketConnection.Act( buf3, ch, null, victim, SocketConnection.MessageTarget.room_vict, true ); } else { SocketConnection.Act( buf4, ch, null, victim, SocketConnection.MessageTarget.character, true ); SocketConnection.Act( buf5, ch, null, victim, SocketConnection.MessageTarget.room, true ); } return; }
/// <summary> /// Steal an object or some coins from a victim. /// </summary> /// <param name="ch"></param> /// <param name="str"></param> public static void Steal(CharData ch, string[] str) { if( ch == null ) return; Object obj = null; CharData victim; bool sleeping = false; string arg1 = String.Empty; string arg2 = String.Empty; string arg = String.Empty; int percent; if (!ch.HasSkill("steal") && !ch.IsAffected(Affect.AFFECT_CHARM)) { ch.SendText("Who are you trying to kid? You couldn't steal shoes from a &n&+mbl&+Mo&n&+ma&+Mte&n&+md&n corpse.\r\n"); return; } if (ch.Riding != null) { ch.SendText("You can't do that while mounted.\r\n"); return; } if (String.IsNullOrEmpty(arg1) || String.IsNullOrEmpty(arg2)) { ch.SendText("Steal what from whom?\r\n"); return; } if ((victim = ch.GetCharRoom(arg2)) == null) { ch.SendText("They aren't here.\r\n"); return; } if (victim == ch) { ch.SendText("That's pointless.\r\n"); return; } if (Combat.IsSafe(ch, victim)) return; if (!ch.IsImmortal()) { ch.WaitState(Skill.SkillList["steal"].Delay); } // Justice stuff Crime.CheckThief(ch, victim); if (ch.IsNPC()) { percent = ch.Level * 2; } else { percent = ((PC)ch).SkillAptitude["steal"]; } percent += ch.GetCurrLuck() / 20; /* Luck */ percent -= victim.Level; /* Character level vs victim's */ if (ch.GetRace() == Race.RACE_HALFLING) { // Halflings get a racial bonus percent += 10; } if (victim.IsAffected(Affect.AFFECT_CURSE)) percent += 15; if (ch.IsAffected(Affect.AFFECT_CURSE)) percent -= 15; if (!victim.IsAwake()) percent += 25; /* Sleeping characters are easier */ if (ch.CheckSneak()) percent += 10; /* Quiet characters steal better */ if (!CharData.CanSee(ch, victim)) percent += 10; /* Unseen characters steal better */ if (!MUDString.IsPrefixOf(arg1, "coins")) { percent = (int)(percent * 1.2); /* Cash is fairly easy to steal */ } else { int number = MUDString.NumberArgument(arg1, ref arg); int count = 0; foreach (Object iobj in victim.Carrying) { if (CharData.CanSeeObj(ch, iobj) && MUDString.NameContainedIn(arg, iobj.Name)) { if (++count == number) { obj = iobj; break; } } } if (!obj) { ch.SendText("You can't find it.\r\n"); return; } if (ch.Level < victim.Level) { // stealing from higher level is possible, but harder percent -= 5 * (victim.Level - ch.Level); } else { // slight bonus for mobs lower level percent += (ch.Level - victim.Level); } if (obj.WearLocation == ObjTemplate.WearLocation.none) /* Items worn are harder */ percent = (int)(percent * .8); else percent = (int)(percent * .4); } ch.PracticeSkill("steal"); if (percent > 85) percent = 85; if (percent < 2) percent = 2; if (percent < MUDMath.NumberPercent()) { /* * Failure. */ //strip sneak ch.RemoveAffect(Affect.AFFECT_SNEAK); // chance of removing invis if (ch.IsAffected(Affect.AFFECT_INVISIBLE) && MUDMath.NumberPercent() > percent) { ch.SendText("You really bungled that attempt.\r\n"); ch.RemoveAffect(Affect.AFFECT_INVISIBLE); } else { ch.SendText("Oops.\r\n"); } SocketConnection.Act("$n&n tried to steal from $N&n.", ch, null, victim, SocketConnection.MessageTarget.everyone_but_victim); if (victim.IsAwake()) { SocketConnection.Act("$n&n tried to steal from you!", ch, null, victim, SocketConnection.MessageTarget.victim); } else { sleeping = true; } // Thief flag for justice. // Added so blind mobs dont hit who ever failed steal from em. if (victim.IsNPC()) { if (!sleeping && !victim.IsBlind()) { CommandType.Interpret(victim, "kill " + ch.Name); } } else { if (!victim.IsBlind() && !sleeping && victim.IsAffected(Affect.AFFECT_BERZERK)) { victim.SendText("In your &+Rblood rage&n, you lash out in anger!\r\n"); CommandType.Interpret(victim, "kill " + ch.Name); } } /* if ( !Macros.IS_SET( ch.actflags, PC.PLAYER_THIEF ) ) { Macros.SET_BIT( ref ch.actflags, PC.PLAYER_THIEF ); buf = String.Format( "{0} became a THIEF by stealing from {1}", ch._name, victim._name ); Immtalk.Immtalk( ch, Immtalk.IMMTALK_CRIME, ch.GetTrust(), buf ); CharData.SavePlayer( ch ); } */ // } if (sleeping) { if (MUDMath.NumberPercent() < victim.GetCurrLuck()) { CommandType.Interpret(victim, "wake"); } } return; } //end failure if (!MUDString.IsPrefixOf(arg1, "coins")) { int amount = victim.GetGold() * MUDMath.NumberRange(1, 20) / 100; int amount2 = victim.GetSilver() * MUDMath.NumberRange(1, 20) / 100; int amount3 = victim.GetCopper() * MUDMath.NumberRange(1, 20) / 100; int amount4 = victim.GetPlatinum() * MUDMath.NumberRange(1, 20) / 100; if ((amount + amount2 + amount3 + amount4) <= 0) { ch.SendText("You couldn't get any &n&+wcoins&n.\r\n"); return; } ch.ReceiveGold(amount); ch.ReceiveSilver(amount2); ch.ReceiveCopper(amount3); ch.ReceivePlatinum(amount4); victim.SpendGold(amount); victim.SpendSilver(amount2); victim.SpendCopper(amount3); victim.SpendPlatinum(amount4); string text = String.Format("Success! You got {0} &+Wplatinum&n, {1} &+Ygold&n, {2} silver, and {3} &+ycopper&n.\r\n", amount2, amount3, amount, amount4); ch.SendText(text); return; } if (!ch.CanDropObject(obj) || obj.HasFlag(ObjTemplate.ITEM_INVENTORY)) { ch.SendText("You can't pry it away.\r\n"); return; } if (ch.CarryNumber + 1 > Limits.MAX_CARRY) { ch.SendText("You have your hands full.\r\n"); return; } if (ch.CarryWeight + obj.GetWeight() > ch.MaxCarryWeight()) { ch.SendText("You cannot carry that much weight.\r\n"); return; } if (obj.WearLocation != ObjTemplate.WearLocation.none) { ch.SendText("Very daring, and you got it!\r\n"); victim.UnequipObject(obj); } obj.RemoveFromChar(); obj.ObjToChar(ch); ch.SendText("Nice work.\r\n"); if (obj.Trap != null && obj.Trap.CheckTrigger( Trap.TriggerType.steal)) { ch.SetOffTrap(obj); if (ch.CurrentPosition == Position.dead) { return; } } return; }
/// <summary> /// Sends death cry message to current room and nearby rooms. /// </summary> /// <param name="ch"></param> void DeathCry( CharData ch ) { int door; string msg = "You hear the animal shriek of $n&n's death cry."; Race.Parts parts = Race.RaceList[ ch.GetRace() ].BodyParts; if( ch == null ) { Log.Error( "Death cry: death cry called with no arguments for CharData!", 0 ); return; } if( !ch.InRoom ) { Log.Error( "Death cry called with ch in no room.", 0 ); return; } switch( MUDMath.NumberRange(1, 10)) { default: msg = "$n&n pitches toward the &n&+yground&n... &+Ldead&n."; break; case 1: msg = "You hear the animal shriek of $n&n's death cry."; break; case 2: msg = "$n&n splatters &n&+rbl&+Ro&n&+ro&+Rd&n all over you."; break; case 3: msg = "$n&n expires quietly."; break; case 4: msg = "$n&n dies writhing in agony."; break; } string mesg = String.Format( "{0}", msg ); SocketConnection.Act( mesg, ch, null, null, SocketConnection.MessageTarget.room ); if( ch.IsNPC() ) msg = "&+lA pained and guttural scream assaults your ears.&n"; else msg = "&+rYour heart races as you hear a death cry nearby.&n"; Room wasInRoom = ch.InRoom; for( door = 0; door < Limits.MAX_DIRECTION; door++ ) { Exit exit; if( ( exit = wasInRoom.ExitData[ door ] ) && exit.TargetRoom && exit.TargetRoom != wasInRoom ) { ch.InRoom = Room.GetRoom(exit.IndexNumber); SocketConnection.Act( msg, ch, null, null, SocketConnection.MessageTarget.room ); } } ch.InRoom = wasInRoom; return; }
/* * Modified to up the damage and allow for a * chance to stun victim or self * damage = (level) d2, for an average of 75 hp at level 50 * stun damage = (level) d3, for an average of 100 hp at level 50 * Player vs player damage is reduced in damage() */ /// <summary> /// Headbutt. Usable to initiate combat and during combat. /// </summary> /// <param name="ch"></param> /// <param name="str"></param> public static void Headbutt(CharData ch, string[] str) { if( ch == null ) return; int chance; int ko; string text; /* Check player's level and class, mobs can use this skill */ if ((!ch.HasSkill("headbutt"))) { ch.SendText("Your skull is much too soft to headbutt anyone.\r\n"); return; } if (ch.IsBlind()) { return; } CharData victim = ch.Fighting; if (str.Length != 0) { if (!(victim = ch.GetCharRoom(str[0])) || victim.CurrentPosition == Position.dead) { ch.SendText("They are nowhere to be seen.\r\n"); return; } } else { if (!victim || victim.CurrentPosition == Position.dead) { ch.SendText("You aren't fighting anyone.\r\n"); return; } } /* anti headbutt me code */ if (ch == victim) { ch.SendText("You get dizzy as you ponder the mechanics of headbutting yourself.\r\n"); return; } if (ch.CurrentPosition < Position.fighting) { ch.SendText("You need to stand up to do that.\r\n"); return; } /* Check size of ch vs. victim. */ /* If ch is too small. */ /* Made it 2 sizes */ if (ch.CurrentSize - 2 > victim.CurrentSize) { SocketConnection.Act("You would crush such a small and delicate being with your mass.", ch, null, victim, SocketConnection.MessageTarget.character); return; } /* Ch 2 or more sizes larger than victim => bad! */ if (ch.CurrentSize + 1 < victim.CurrentSize) { SocketConnection.Act("You can't reach their head!", ch, null, victim, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n slams $s head into your thigh.", ch, null, victim, SocketConnection.MessageTarget.victim); SocketConnection.Act("$n&n slams $s head into $N's thigh.", ch, null, victim, SocketConnection.MessageTarget.room_vict); ch.WaitState((Skill.SkillList["headbutt"].Delay * 9) / 10); if (victim.Fighting == null) { Combat.SetFighting(victim, ch); } return; } ch.WaitState(MUDMath.FuzzyNumber(Skill.SkillList["headbutt"].Delay)); ch.PracticeSkill("headbutt"); if (!ch.Fighting) { Combat.SetFighting(ch, victim); } if (!victim.Fighting) { Combat.SetFighting(victim, ch); } /* Added a PC skill level */ // Chance was over-downgraded at some point. Was skill level - 5%, // then it was changed to skill level / 2 giving a level 50 a headbutt // success rate of about 47%. I've upped it to 4/5 of skill level, // meaning that a level 50 has a success rate of 76%, which is a good // target success rate. Keep in mind minotaur will have a success rate of // about 83%. if (ch.IsNPC()) { chance = 50 + ch.Level; } else { chance = ((PC)ch).SkillAptitude["headbutt"] * 4 / 5; } // Minotaur headbutt bonus if (ch.GetRace() == Race.RACE_MINOTAUR) { chance += 7; } if (victim.CurrentPosition < Position.fighting) { chance /= 3; } if (MUDMath.NumberPercent() < chance) /* Headbutt successful, possible KO */ { /* First give the victim a chance to dodge */ if (Combat.CheckDodge(ch, victim)) { return; } /* OK, lets settle for stun right now * a skill level of 100% has a 20% chance of stun * a skill level of 50% has a 2.5% chance of stun * a skill level of 23% has a 1% chance of stun * immortals get a 15% bonus */ // The stun math was bad. Stun was way more often that it should have // been. Now we have a flat /4 chance, meaning a the following stun chances // at each skill level: // 25 = 5% 50 = 10 % 75 = 15% 95 = 19% ko = chance / 4; if (ch.IsImmortal()) { ko += 15; } text = String.Format("Commandheadbutt: {0}&n attempting a KO with {1}%% chance.", ch.Name, ko); ImmortalChat.SendImmortalChat(null, ImmortalChat.IMMTALK_SPAM, 0, text); if (MUDMath.NumberPercent() < ko + 1) { // deal some decent damage // This was previously level d 3 which was fairly pathetic. // Level d 8 = 50 min, 400 max at level 50 with an average of 225 // PvP damage is quartered, so headbutt will do about 56 against a player. if (ch.GetRace() != Race.RACE_MINOTAUR) { Combat.InflictDamage(ch, victim, MUDMath.Dice(ch.Level, 8), "headbutt", ObjTemplate.WearLocation.none, AttackType.DamageType.bludgeon); } else { Combat.InflictDamage(ch, victim, MUDMath.Dice(ch.Level, 9), "headbutt", ObjTemplate.WearLocation.none, AttackType.DamageType.bludgeon); } if (victim.CurrentPosition > Position.stunned) { SocketConnection.Act("$n&n staggers about, then collapses in a heap.", victim, null, null, SocketConnection.MessageTarget.room); victim.SendText("You fall to the ground &+Wstunned&n.\r\n"); SocketConnection.Act("$n&n is &+Wstunned!&n", victim, null, null, SocketConnection.MessageTarget.room); victim.CurrentPosition = Position.stunned; victim.WaitState((Skill.SkillList["headbutt"].Delay)); text = String.Format("Commandheadbutt: {0}&n stunned {1}&n.", ch.Name, victim.Name); ImmortalChat.SendImmortalChat(null, ImmortalChat.IMMTALK_SPAM, 0, text); } } else { // just your ordinary headbutt damage // This was previously level d 2 which was fairly pathetic. // Level d 6 = 50 min, 300 max at level 50 with an average of 175 // PvP damage is quartered, so headbutt will do about 43 against a player. if (ch.GetRace() != Race.RACE_MINOTAUR) { if (!Combat.InflictDamage(ch, victim, MUDMath.Dice(ch.Level, 6), "headbutt", ObjTemplate.WearLocation.none, AttackType.DamageType.bludgeon)) { // Someone blasts you in the head it'll definitely stun you for 3/4 of a second. victim.WaitState(3); } } else { if (!Combat.InflictDamage(ch, victim, MUDMath.Dice(ch.Level, 7), "headbutt", ObjTemplate.WearLocation.none, AttackType.DamageType.bludgeon)) { // Someone blasts you in the head with horns it'll definitely stun you for a second. // -- Xangis victim.WaitState(4); } } } } else /* Headbutt failed, possible damgage to self, possible KO of self */ { /* Don't allow char to kill self, just mort self */ /* Give them a chance to not take any damage */ // Checking chance instead of player's level. Since this should be based on skill // this should be about right (average of 24% chance of screwing yourself at level 50 // instead of 50%, 17% chance for minos). if (MUDMath.NumberPercent() < chance) { ch.SendText("Your headbutt fails to strike its target.\r\n"); SocketConnection.Act("$n&n tries to headbutt $N&n but can't quite connect.", ch, null, victim, SocketConnection.MessageTarget.everyone_but_victim); SocketConnection.Act("$n&n bobs around you in a feeble attempt at a headbutt.", ch, null, victim, SocketConnection.MessageTarget.victim); return; } SocketConnection.Act("You bang your head against the brick wall of $N&n.", ch, null, victim, SocketConnection.MessageTarget.character); SocketConnection.Act("$n&n tries a headbutt but $N&n gets the best of $m.", ch, null, victim, SocketConnection.MessageTarget.everyone_but_victim); SocketConnection.Act("$n&n bangs into you in a feeble attempt at a headbutt.", ch, null, victim, SocketConnection.MessageTarget.victim); // KO chance of 24% for normals, 17% for minos. // You have to fail three checks to get your ass kicked by KO, one for the actual skill check, // one for the damage check and finally one for the KO check. // keep in mind this KO does damage of about 100 to self at level 50, which is a hell of a lot // for a failed skill. // The chance of ko'ing yourself at each skill level is as follows: (after all 3 checks) // Skill 25 = 59.2% Skill 50 = 29.6% Skill 75 = 14.4% Skill 95 = 9.38% ko = 108 - chance; int dam; if (MUDMath.NumberPercent() < ko) { // doh! This is gonna hurt //deal some decent damage...to self! if (ch.GetRace() != Race.RACE_MINOTAUR) { dam = MUDMath.Dice(ch.Level, 3); } else { dam = MUDMath.Dice(ch.Level, 2); } if (dam > ch.Hitpoints) { dam = ch.Hitpoints + 1; } if (Combat.InflictDamage(ch, ch, dam, "headbutt", ObjTemplate.WearLocation.none, AttackType.DamageType.bludgeon)) { // ch invalidated, can't send messages. return; } SocketConnection.Act("$n&n staggers about, then collapses in a heap.", ch, null, null, SocketConnection.MessageTarget.room); ch.SendText("You fall to the ground stunned.\r\n"); SocketConnection.Act("$n&n is stunned!", ch, null, null, SocketConnection.MessageTarget.room); ch.CurrentPosition = Position.stunned; ch.WaitState((Skill.SkillList["headbutt"].Delay * 2)); text = String.Format("Commandheadbutt: {0}&n stunned self.", ch.Name); ImmortalChat.SendImmortalChat(null, ImmortalChat.IMMTALK_SPAM, 0, text); } else { // Was previously level d 2, which averaged 30 damage at 20 and 75 at 50. PC to PC // damage is not quartered when it is done to yourself, so this it kind of high at the // upper levels. This has been reduced by level / 5, so the damage at 50 averages 65 // instead of 65. // Keep in mind that the real penalties come from KO and comparitively someone that // fails a bash doesen't take insane damage. dam = MUDMath.Dice(ch.Level, 2) - ch.Level / 5; if (dam > ch.Hitpoints) { dam = ch.Hitpoints + 1; } Combat.InflictDamage(ch, ch, dam, "headbutt", ObjTemplate.WearLocation.none, AttackType.DamageType.bludgeon); } } //end if() headbutt failed return; }
/// <summary> /// Checks whether the object can be worn by the character and provides a message /// to the character if it can't be. /// </summary> /// <param name="ch"></param> /// <returns></returns> public bool IsWearableBy(CharData ch) { if (ch.IsImmortal() && ch.HasActionBit(PC.PLAYER_GODMODE)) return true; if (ch.IsClass(CharClass.Names.monk)) { if (_weight > (1 + (ch.Level / 15))) { SocketConnection.Act("$p&n is too heavy for you to use.", ch, this, null, SocketConnection.MessageTarget.character); return false; } } if (HasWearFlag(ObjTemplate.USE_ANYONE)) return true; switch (ch.Gender) { case MobTemplate.Sex.male: if (HasAntiFlag(ObjTemplate.USE_ANTIMALE)) { SocketConnection.Act("$p&n is too dainty for one of such as yourself.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case MobTemplate.Sex.female: if (HasAntiFlag(ObjTemplate.USE_ANTIFEMALE)) { SocketConnection.Act("$p&n is too bulky and poorly designed for one as skilled as you.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case MobTemplate.Sex.neutral: if (HasAntiFlag(ObjTemplate.USE_ANTINEUTER)) { SocketConnection.Act("$p&n was not designed for your form.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; default: SocketConnection.Act("$p&n cannot be used by you because your sex is buggy.", ch, this, null, SocketConnection.MessageTarget.character); return false; } switch (ch.CharacterClass.ClassNumber) { default: break; case CharClass.Names.warrior: if (!HasAntiFlag(ObjTemplate.USE_WARRIOR)) { SocketConnection.Act("You won't abide such inferiority as found in $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.ranger: case CharClass.Names.hunter: if (!HasAntiFlag(ObjTemplate.USE_RANGER)) { SocketConnection.Act("Your professionalism prevents you from using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.paladin: if (!HasAntiFlag(ObjTemplate.USE_PALADIN)) { SocketConnection.Act("Your convictions prevents you from using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.antipaladin: if (!HasAntiFlag(ObjTemplate.USE_ANTI)) { SocketConnection.Act("Your skill and pride prevents you from using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.cleric: if (!HasAntiFlag(ObjTemplate.USE_CLERIC)) { SocketConnection.Act("Your faith prevents you from using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.druid: if (!HasAntiFlag(ObjTemplate.USE_DRUID)) { SocketConnection.Act("Your path prevents you from using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.shaman: if (!HasAntiFlag(ObjTemplate.USE_SHAMAN)) { SocketConnection.Act("You lack the proper know-how to use $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.sorcerer: case CharClass.Names.illusionist: if (!HasAntiFlag(ObjTemplate.USE_SORCERER)) { SocketConnection.Act("You neglected use of $p&n in deference to your magics.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.elementAir: case CharClass.Names.elementEarth: case CharClass.Names.elementFire: case CharClass.Names.elementWater: if (!HasAntiFlag(ObjTemplate.USE_ELEMENTAL)) { SocketConnection.Act("You have better things to waste time on than $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.thief: if (!HasAntiFlag( ObjTemplate.USE_THIEF)) { SocketConnection.Act("Using $p&n would only hinder your abilities.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.psionicist: if (!HasAntiFlag(ObjTemplate.USE_PSI)) { SocketConnection.Act("The thought of using $p&n makes your brain hurt.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.assassin: if (!HasAntiFlag(ObjTemplate.USE_ASSASSIN)) { SocketConnection.Act("You never studied the art behind $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.mercenary: if (!HasAntiFlag( ObjTemplate.USE_MERCENARY)) { SocketConnection.Act("Your training never addressed using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case CharClass.Names.mystic: case CharClass.Names.monk: if (!HasAntiFlag( ObjTemplate.USE_MONK)) { SocketConnection.Act("You cannot use $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; } switch (ch.GetRace()) { default: break; case Race.RACE_HUMAN: if (HasAntiFlag( ObjTemplate.USE_NOHUMAN)) { SocketConnection.Act("Your race is ill fit to use $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_GREYELF: if (HasAntiFlag(ObjTemplate.USE_NOGREYELF)) { SocketConnection.Act("Your race is too weak to handle $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_HALFELF: if (HasAntiFlag( ObjTemplate.USE_NOHALFELF)) { SocketConnection.Act("Your bastardized nature prevents you from using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_DWARF: if (HasAntiFlag( ObjTemplate.USE_NODWARF)) { SocketConnection.Act("Your body wasn't designed to use $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_HALFLING: if (HasAntiFlag( ObjTemplate.USE_NOGNOME)) { SocketConnection.Act("Just contemplating $p&n makes you long for food.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_BARBARIAN: if (HasAntiFlag( ObjTemplate.USE_NOBARBARIAN)) { SocketConnection.Act("Your size and mass prevents you from using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_DUERGAR: if (HasAntiFlag( ObjTemplate.USE_NODUERGAR)) { SocketConnection.Act("Of what use is $p&n to a &n&+rduergar&n?", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_DROW: if (HasAntiFlag( ObjTemplate.USE_NODROW)) { SocketConnection.Act("The design of $p&n is too inferior for use by a &n&+mdrow&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_TROLL: if (HasAntiFlag( ObjTemplate.USE_NOTROLL)) { SocketConnection.Act("You try to use $p&n, but become quickly frustrated.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_OGRE: if (HasAntiFlag( ObjTemplate.USE_NOOGRE)) { SocketConnection.Act("You can't quite figure out how to use $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_ORC: if (HasAntiFlag( ObjTemplate.USE_NOORC)) { SocketConnection.Act("Obviously, $p&n was not crafted for an &+Lorc&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_CENTAUR: if (HasAntiFlag( ObjTemplate.USE_NOCENTAUR)) { SocketConnection.Act("Your find $p&n's design unsuitable for a &n&+gcen&+Ltaur&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_GITHYANKI: if (HasAntiFlag( ObjTemplate.USE_NOGITHYANKI)) { SocketConnection.Act("You find $p&n to be unfitting for a &+Wgith&+Gyanki&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_GITHZERAI: if (HasAntiFlag( ObjTemplate.USE_NOGITHZERAI)) { SocketConnection.Act("Your nature prevents you from using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_MINOTAUR: if (HasAntiFlag( ObjTemplate.USE_NOMINOTAUR)) { SocketConnection.Act("Your distinctive build prevents your use of $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_GOBLIN: if (HasAntiFlag( ObjTemplate.USE_NOGOBLIN)) { SocketConnection.Act("Your stature and build make it impossible for you to use $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_RAKSHASA: if (HasAntiFlag( ObjTemplate.USE_NORAKSHASA)) { SocketConnection.Act("Your feline structure inhibits you from using $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RACE_GNOLL: if (HasAntiFlag( ObjTemplate.USE_NOGNOLL)) { SocketConnection.Act("Your canine features hinder the use of $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; } switch (ch.GetRacewarSide()) { case Race.RacewarSide.good: if (HasAntiFlag( ObjTemplate.USE_ANTIGOODRACE)) { SocketConnection.Act("Your &n&+cs&+Co&n&+cu&+Cl&n is too &+Wpure&n to use $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; case Race.RacewarSide.evil: if (HasAntiFlag( ObjTemplate.USE_ANTIEVILRACE)) { SocketConnection.Act("Your &+Lblack &n&+rhe&+Ra&n&+rr&+Rt&n prevents use of $p&n.", ch, this, null, SocketConnection.MessageTarget.character); return false; } break; default: break; } return true; }