diff --git a/Info/NeedsFinished.txt b/Info/NeedsFinished.txt index 329a78418..7fe51bd39 100644 --- a/Info/NeedsFinished.txt +++ b/Info/NeedsFinished.txt @@ -23,4 +23,11 @@ CentrED Stuff Dungeon Bosses - Dungeon Arenas - Duneon Arenas Region + Skills + +PortalSpawners + - Need to spawn (regon spawner?) + - Need to add more mobs. + +Random Citizens diff --git a/Projects/UOContent/Engines/PortalSpawner.cs b/Projects/UOContent/Engines/PortalSpawner.cs index c4df887b3..d80d8af9d 100644 --- a/Projects/UOContent/Engines/PortalSpawner.cs +++ b/Projects/UOContent/Engines/PortalSpawner.cs @@ -12,7 +12,7 @@ namespace Server.Custom.Events private bool _triggered; [Constructible] - public PortalSpawner() : base(0x0F6C) + public PortalSpawner() : base(0xFF93) { Name = "a horrific tear"; Hue = 1157; @@ -24,6 +24,28 @@ namespace Server.Custom.Events { } + private bool _zAdjusted; + + public override void OnMapChange() + { + base.OnMapChange(); + + // Now it checks the moment it officially enters the world + if (!_zAdjusted && Map != null && Map != Map.Internal) + { + _zAdjusted = true; + + // Wait one tick for the spawner to finish snapping it to the floor + Timer.DelayCall(TimeSpan.Zero, () => + { + if (!Deleted) + { + Z += 5; // Adjust as needed + } + }); + } + } + public override bool HandlesOnMovement => true; public override void OnMovement(Mobile m, Point3D oldLocation) diff --git a/Projects/UOContent/Items/Custom/CrystalOfFortitude.cs b/Projects/UOContent/Items/Custom/CrystalOfFortitude.cs new file mode 100644 index 000000000..fa0b99b36 --- /dev/null +++ b/Projects/UOContent/Items/Custom/CrystalOfFortitude.cs @@ -0,0 +1,59 @@ +using System; +using Server; +using Server.Mobiles; +using ModernUO.Serialization; + +namespace Server.Items +{ + [SerializationGenerator(0)] + public partial class CrystalOfFortitude : Item + { + [Constructible] + public CrystalOfFortitude() : base(0x1F19) // Matching crystal graphic + { + Name = "a crystalline shard of fortitude"; + Hue = 0x21; // A deep, vibrant red hue to represent physical stats + Weight = 1.0; + } + + public override void OnDoubleClick(Mobile from) + { + if (!IsChildOf(from.Backpack)) + { + from.SendLocalizedMessage(1042001); // "That must be in your pack for you to use it." + return; + } + + // Standard stats are handled as simple integers (unlike skills which use tenths) + int maxCap = 250; + int capIncrease = 1; + + if (from.StatCap >= maxCap) + { + from.SendMessage(38, "Your physical form cannot be pushed any further. You are already at the absolute maximum stat cap."); + return; + } + + // Apply the permanent stat cap increase + from.StatCap += capIncrease; + + // Failsafe in case a GM accidentally sets them too high + if (from.StatCap > maxCap) + { + from.StatCap = maxCap; + } + + // Theatrical effects + from.SendMessage(0x21, "The crystal shatters, surging through your veins! Your total stat cap has permanently increased by 1."); + from.PlaySound(0x1F8); + from.FixedParticles(0x373A, 10, 15, 5012, EffectLayer.Waist); + + // Destroy the item + Delete(); + } + + public CrystalOfFortitude(Serial serial) : base(serial) + { + } + } +} diff --git a/Projects/UOContent/Items/Custom/CrystalOfInsight.cs b/Projects/UOContent/Items/Custom/CrystalOfInsight.cs new file mode 100644 index 000000000..75709410f --- /dev/null +++ b/Projects/UOContent/Items/Custom/CrystalOfInsight.cs @@ -0,0 +1,60 @@ +using System; +using Server; +using Server.Mobiles; +using ModernUO.Serialization; + +namespace Server.Items +{ + [SerializationGenerator(0)] + public partial class CrystalOfInsight : Item + { + [Constructible] + public CrystalOfInsight() : base(0x1F19) // A cool crystal/gem graphic + { + Name = "a crystalline shard of insight"; + Hue = 0x47E; // Matches the Diamond Core stance hue + Weight = 1.0; + } + + public override void OnDoubleClick(Mobile from) + { + // Security check: Must be in the player's main backpack to use + if (!IsChildOf(from.Backpack)) + { + from.SendLocalizedMessage(1042001); // "That must be in your pack for you to use it." + return; + } + + // In UO integer logic, 720.0 is 7200, and 1.0 is 10 + int maxCap = 7200; + int capIncrease = 10; + + if (from.SkillsCap >= maxCap) + { + from.SendMessage(38, "Your mind cannot expand any further. You are already at the absolute maximum skill cap."); + return; + } + + // Apply the permanent increase + from.SkillsCap += capIncrease; + + // Failsafe: If they somehow used a +1.0 item while at 719.5, hard-cap it at exactly 720.0 + if (from.SkillsCap > maxCap) + { + from.SkillsCap = maxCap; + } + + // Theatrical effects to make the reward feel powerful + from.SendMessage(0x47E, "The crystal shatters in your hands, expanding your mind! Your total skill cap has permanently increased by 1.0."); + from.PlaySound(0x1F8); // Shatter sound (same as the Titan's stance shift) + from.FixedParticles(0x373A, 10, 15, 5012, EffectLayer.Waist); // Sparkle effect around the player + + // Destroy the item now that it has been consumed + Delete(); + } + + public CrystalOfInsight(Serial serial) : base(serial) + { + } + } +} diff --git a/Projects/UOContent/Items/Custom/CrystalOfKnowledge.cs b/Projects/UOContent/Items/Custom/CrystalOfKnowledge.cs new file mode 100644 index 000000000..31aa580a3 --- /dev/null +++ b/Projects/UOContent/Items/Custom/CrystalOfKnowledge.cs @@ -0,0 +1,100 @@ +using System; +using Server; +using Server.Mobiles; + +namespace Server.Items +{ + public class CrystalOfKnowledge : Item + { + private SkillName _Skill; + + [CommandProperty(AccessLevel.GameMaster)] + public SkillName Skill + { + get => _Skill; + set + { + _Skill = value; + UpdateName(); + } + } + + [Constructible] + public CrystalOfKnowledge() : this((SkillName)Utility.Random(SkillInfo.Table.Length)) + { + } + + [Constructible] + public CrystalOfKnowledge(SkillName skill) : base(0x1F19) + { + _Skill = skill; + Hue = 0x501; + Weight = 1.0; + UpdateName(); + } + + private void UpdateName() + { + Name = $"a minor shard of {_Skill}"; + } + + public override void OnDoubleClick(Mobile from) + { + if (!IsChildOf(from.Backpack)) + { + from.SendLocalizedMessage(1042001); + return; + } + + Skill targetSkill = from.Skills[_Skill]; + + if (targetSkill == null) + return; + + int maxCap = 120; + int capIncrease = 1; + + if (targetSkill.Cap >= maxCap) + { + from.SendMessage(38, $"Your mastery of {_Skill} is already at its absolute limit."); + return; + } + + targetSkill.Cap += capIncrease; + + if (targetSkill.Cap > maxCap) + { + targetSkill.Cap = maxCap; + } + + from.SendMessage(0x501, $"The shard dissolves, permanently increasing your {_Skill} cap by 1.0!"); + from.PlaySound(0x243); + from.FixedParticles(0x373A, 10, 15, 5012, EffectLayer.Waist); + + Delete(); + } + + public CrystalOfKnowledge(Serial serial) : base(serial) + { + } + + // --- ModernUO Manual Serialization --- + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + writer.Write(0); // Version + + // Save the specific skill ID to the hard drive + writer.Write((int)_Skill); + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + int version = reader.ReadInt(); + + // Load the specific skill ID back when the server boots + _Skill = (SkillName)reader.ReadInt(); + } + } +} diff --git a/Projects/UOContent/Engines/DungeonBoss/Alters/BossAlter.cs b/Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Alters/BossAlter.cs similarity index 100% rename from Projects/UOContent/Engines/DungeonBoss/Alters/BossAlter.cs rename to Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Alters/BossAlter.cs diff --git a/Projects/UOContent/Engines/DungeonBoss/Alters/ShameAlter.cs b/Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Alters/ShameAlter.cs similarity index 88% rename from Projects/UOContent/Engines/DungeonBoss/Alters/ShameAlter.cs rename to Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Alters/ShameAlter.cs index 1c95f6b47..b6ebfe989 100644 --- a/Projects/UOContent/Engines/DungeonBoss/Alters/ShameAlter.cs +++ b/Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Alters/ShameAlter.cs @@ -11,7 +11,7 @@ namespace Server.Custom.Dungeons { public override Type SummonItem => typeof(PulsingBloodClot); - public override Type BossType => typeof(BloodstoneTitan); + public override Type BossType => typeof(CrystallineTitan); [Constructible] public ShameAlter() : base(0xFFBD) diff --git a/Projects/UOContent/Engines/DungeonBoss/Items/PulsingBloodClot.cs b/Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Items/PulsingBloodClot.cs similarity index 100% rename from Projects/UOContent/Engines/DungeonBoss/Items/PulsingBloodClot.cs rename to Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Items/PulsingBloodClot.cs diff --git a/Projects/UOContent/Engines/DungeonBoss/Mobiles/BloodstoneTitan.cs b/Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Mobiles/BloodstoneTitan.cs similarity index 100% rename from Projects/UOContent/Engines/DungeonBoss/Mobiles/BloodstoneTitan.cs rename to Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Mobiles/BloodstoneTitan.cs diff --git a/Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Mobiles/CrystallineTitan.cs b/Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Mobiles/CrystallineTitan.cs new file mode 100644 index 000000000..7487dada3 --- /dev/null +++ b/Projects/UOContent/Mobiles/Monsters/Custom/DungeonBoss/Mobiles/CrystallineTitan.cs @@ -0,0 +1,153 @@ +using System; +using Server; +using Server.Items; +using Server.Network; +using ModernUO.Serialization; + +namespace Server.Mobiles +{ + [SerializationGenerator(0, false)] + public partial class CrystallineTitan : BaseCreature + { + private DateTime _NextStanceChange; + private TitanStance _CurrentStance; + + private enum TitanStance + { + Normal, + DiamondCore, + Shattered + } + + [Constructible] + public CrystallineTitan() : base(AIType.AI_Melee, FightMode.Closest) + { + Name = "The Crystalline Titan"; + Body = 1109; + BaseSoundID = 0x127; + + SetStr(800, 1000); + SetDex(120, 150); + SetInt(100, 150); + SetHits(15000, 20000); + + SetSkill(SkillName.Tactics, 100.0, 120.0); + SetSkill(SkillName.Wrestling, 100.0, 120.0); + SetSkill(SkillName.MagicResist, 120.0, 150.0); + SetSkill(SkillName.Anatomy, 100.0, 120.0); + + Fame = 25000; + Karma = -25000; + VirtualArmor = 80; + + // Start in the normal stance to show off the raw graphic + EnterNormal(); + + // Give players 30 seconds of normal combat before the first special phase triggers + _NextStanceChange = DateTime.UtcNow + TimeSpan.FromSeconds(30); + } + + public override void OnThink() + { + base.OnThink(); + + if (Deleted || !Alive || Combatant == null) + return; + + // Trigger the swap every 30 seconds while fighting + if (DateTime.UtcNow > _NextStanceChange) + { + // Reset the clock immediately so OnThink doesn't loop + _NextStanceChange = DateTime.UtcNow + TimeSpan.FromSeconds(30); + + if (_CurrentStance == TitanStance.DiamondCore || _CurrentStance == TitanStance.Shattered) + { + SpeechHue = 0x3B2; + Say("*The Titan's crystals begin to stabilize...*"); + + // Wait 2 seconds, then execute the stat/hue change + Timer.DelayCall(TimeSpan.FromSeconds(2.0), () => { if (Alive && !Deleted) EnterNormal(); }); + } + else + { + if (Utility.RandomBool()) + { + SpeechHue = 0x47E; + Say("*The Titan braces itself as its shell begins to harden!*"); + + Timer.DelayCall(TimeSpan.FromSeconds(2.0), () => { if (Alive && !Deleted) EnterDiamondCore(); }); + } + else + { + SpeechHue = 0x22; + Say("*The Titan's crystals throb with unstable, violent energy!*"); + + Timer.DelayCall(TimeSpan.FromSeconds(2.0), () => { if (Alive && !Deleted) EnterShattered(); }); + } + } + } + } + + private void EnterNormal() + { + _CurrentStance = TitanStance.Normal; + Hue = 0; + + SetResistance(ResistanceType.Physical, 50, 60); + SetResistance(ResistanceType.Energy, 50, 60); + SetResistance(ResistanceType.Fire, 50, 60); + SetResistance(ResistanceType.Cold, 50, 60); + SetResistance(ResistanceType.Poison, 60, 70); + + SetDamage(30, 45); + + PlaySound(0x11F); // Deep rumble sound as the hue shifts + } + + private void EnterDiamondCore() + { + _CurrentStance = TitanStance.DiamondCore; + Hue = 0x47E; + + SetResistance(ResistanceType.Physical, 80, 90); + SetResistance(ResistanceType.Energy, 70, 80); + SetResistance(ResistanceType.Fire, 60, 70); + SetResistance(ResistanceType.Cold, 60, 70); + SetResistance(ResistanceType.Poison, 100, 100); + + SetDamage(15, 25); + + PlaySound(0x1E5); // Hardening/magic sound as the hue shifts + } + + private void EnterShattered() + { + _CurrentStance = TitanStance.Shattered; + Hue = 0x86; + + SetResistance(ResistanceType.Physical, 30, 40); + SetResistance(ResistanceType.Energy, 30, 40); + SetResistance(ResistanceType.Fire, 30, 40); + SetResistance(ResistanceType.Cold, 30, 40); + + SetDamage(45, 65); + + PlaySound(0x1F8); // Shattering explosion sound as the hue shifts + } + + // --- Boss Immunities --- + public override bool AutoDispel => true; + public override bool BardImmune => true; + public override bool Uncalmable => true; + public override Poison PoisonImmune => Poison.Lethal; + + public override void GenerateLoot() + { + AddLoot(LootPack.SuperBoss, 2); + } + + public CrystallineTitan(Serial serial) : base(serial) + { + } + } +} diff --git a/Run.sh b/Run.sh index 3bebdd950..58755fbac 100755 --- a/Run.sh +++ b/Run.sh @@ -9,5 +9,7 @@ else echo "________________________________" echo "| Starting Server...." echo "--------------------------------" - dotnet publish Projects/Application/Application.csproj -c Release -r linux-x64 --self-contained=false && dotnet ./Distribution/ModernUO.dll + #dotnet publish Projects/Application/Application.csproj -c Release -r linux-x64 --self-contained=false && + #sleep 2 && + dotnet ./Distribution/ModernUO.dll fi