Explosion Potions

Discussion in 'Era Discussion' started by Tater Salad, Dec 9, 2012.

  1. Tater Salad

    Tater Salad Renaissance Volunteers
    Renaissance Volunteers

    Joined:
    Nov 15, 2012
    Messages:
    312
    Likes Received:
    15
    Should you decide to make it so you can only throw one potion at a time. Here is a BaseExplosionPotion.cs that will only allow one potion, and use the same potion again should the target be cancelled.

    Commented out is a random timer. Either need to decrement states[1] before the "if" statement, or change the call to pass RandomMinMax(2, 3) instead.


    Code:
    using System;
    using System.Collections;
    using Server;
    using Server.Network;
    using Server.Targeting;
    using Server.Spells;
    using Server.Mobiles;
    
    namespace Server.Items
    {
    	public abstract class BaseExplosionPotion : BasePotion
    	{
    		public abstract int MinDamage { get; }
    		public abstract int MaxDamage { get; }
    
    		public override bool RequireFreeHand{ get{ return false; } }
    
    		private static bool LeveledExplosion = false; // Should explosion potions explode other nearby potions?
    		private static bool InstantExplosion = false; // Should explosion potions explode on impact?
    		private const int   ExplosionRange   = 2;     // How long is the blast radius?
    
    		public BaseExplosionPotion( PotionEffect effect ) : base( 0xF0D, effect )
    		{
    		}
    
    		public BaseExplosionPotion( Serial serial ) : base( serial )
    		{
    		}
    
    		public override void Serialize( GenericWriter writer )
    		{
    			base.Serialize( writer );
    
    			writer.Write( (int) 0 ); // version
    		}
    
    		public override void Deserialize( GenericReader reader )
    		{
    			base.Deserialize( reader );
    
    			int version = reader.ReadInt();
    		}
    
    		public virtual object FindParent( Mobile from )
    		{
    			Mobile m = this.HeldBy;
    
    			if ( m != null && m.Holding == this )
    				return m;
    
    			object obj = this.RootParent;
    
    			if ( obj != null )
    				return obj;
    
    			if ( Map == Map.Internal )
    				return from;
    
    			return this;
    		}
    
    		private Timer m_Timer;
    
    		private ArrayList m_Users;
    
    		public override void Drink( Mobile from )
    		{
    			if ( Core.AOS && (from.Paralyzed || from.Frozen || (from.Spell != null && from.Spell.IsCasting)) )
    			{
    				from.SendLocalizedMessage( 1062725 ); // You can not use a purple potion while paralyzed.
    				return;
    			}
    
    			if (from is PlayerMobile) 
    			{
    				PlayerMobile pm = from as PlayerMobile;
    				if (DateTime.Now - pm.LastExplo < TimeSpan.FromSeconds( 5.0 ) )
    				{
    					if ( m_Timer != null )
    					{				
    						from.Target = new ThrowTarget( this );
    						return;
    					}
    
    					pm.NonlocalOverheadMessage( 0, 0x22, false, "You must wait a few seconds before you can use another explosion potion." );
    					return;
    				}
    
    				pm.LastExplo = DateTime.Now;
    			} 
    
    			ThrowTarget targ = from.Target as ThrowTarget;
    
    			if ( targ != null && targ.Potion == this )
    				return;
    
    			from.RevealingAction();
    
    			if ( m_Users == null )
    				m_Users = new ArrayList();
    
    			if ( !m_Users.Contains( from ) )
    				m_Users.Add( from );
    
    			from.Target = new ThrowTarget( this );
    
    			if ( m_Timer == null )
    			{
    				from.SendLocalizedMessage( 500236 ); // You should throw it now!
    				//m_Timer = Timer.DelayCall( TimeSpan.FromSeconds( 0.75 ), TimeSpan.FromSeconds( 1.0 ), 4, new TimerStateCallback( Detonate_OnTick ), new object[]{ from, Utility.RandomMinMax(3, 4) });
    				m_Timer = Timer.DelayCall( TimeSpan.FromSeconds( 0.75 ), TimeSpan.FromSeconds( 1.0 ), 4, new TimerStateCallback( Detonate_OnTick ), new object[]{ from, 3 });
    			}
    		}
    
    		private void Detonate_OnTick( object state )
    		{
    			if ( Deleted )
    				return;
    
    			object[] states = (object[])state;
    			Mobile from = (Mobile)states[0];
    			int timer = (int)states[1];
    
    
    			object parent = FindParent( from );
    
    			if ( timer == 0 )
    			{
    				Point3D loc;
    				Map map;
    
    				if ( parent is Item )
    				{
    					Item item = (Item)parent;
    
    					loc = item.GetWorldLocation();
    					map = item.Map;
    				}
    				else if ( parent is Mobile )
    				{
    					Mobile m = (Mobile)parent;
    
    					loc = m.Location;
    					map = m.Map;
    				}
    				else
    				{
    					return;
    				}
    
    				Explode( from, true, loc, map );
    			}
    			else
    			{
    				if ( parent is Item )
    					((Item)parent).PublicOverheadMessage( MessageType.Regular, 0x22, false, timer.ToString() );
    				else if ( parent is Mobile )
    					((Mobile)parent).PublicOverheadMessage( MessageType.Regular, 0x22, false, timer.ToString() );
    
    				states[1] = timer - 1;
    			}
    		}
    
    		private void Reposition_OnTick( object state )
    		{
    			if ( Deleted )
    				return;
    
    			object[] states = (object[])state;
    			Mobile from = (Mobile)states[0];
    			IPoint3D p = (IPoint3D)states[1];
    			Map map = (Map)states[2];
    
    			Point3D loc = new Point3D( p );
    
    			if ( InstantExplosion )
    				Explode( from, true, loc, map );
    			else
    				MoveToWorld( loc, map );
    		}
    
    		private class ThrowTarget : Target
    		{
    			private BaseExplosionPotion m_Potion;
    
    			public BaseExplosionPotion Potion
    			{
    				get{ return m_Potion; }
    			}
    
    			public ThrowTarget( BaseExplosionPotion potion ) : base( 12, true, TargetFlags.None )
    			{
    				m_Potion = potion;
    			}
    
    			protected override void OnTarget( Mobile from, object targeted )
    			{
    				if ( m_Potion.Deleted || m_Potion.Map == Map.Internal )
    					return;
    
    				IPoint3D p = targeted as IPoint3D;
    
    				if ( p == null )
    					return;
    
    				Map map = from.Map;
    
    				if ( map == null )
    					return;
    
    				SpellHelper.GetSurfaceTop( ref p );
    
    				from.RevealingAction();
    
    				IEntity to;
    
    				if ( p is Mobile )
    					to = (Mobile)p;
    				else
    					to = new Entity( Serial.Zero, new Point3D( p ), map );
    
    				Effects.SendMovingEffect( from, to, m_Potion.ItemID & 0x3FFF, 7, 0, false, false, m_Potion.Hue, 0 );
    
    				m_Potion.Internalize();
    				Timer.DelayCall( TimeSpan.FromSeconds( 1.0 ), new TimerStateCallback( m_Potion.Reposition_OnTick ), new object[]{ from, p, map } );
    			}
    		}
    
    		public void Explode( Mobile from, bool direct, Point3D loc, Map map )
    		{
    			if ( Deleted )
    				return;
    
    			Delete();
    
    			for ( int i = 0; m_Users != null && i < m_Users.Count; ++i )
    			{
    				Mobile m = (Mobile)m_Users[i];
    				ThrowTarget targ = m.Target as ThrowTarget;
    
    				if ( targ != null && targ.Potion == this )
    					Target.Cancel( m );
    			}
    
    			if ( map == null )
    				return;
    
    			Effects.PlaySound( loc, map, 0x207 );
    			Effects.SendLocationEffect( loc, map, 0x36BD, 20 );
    
    			int alchemyBonus = 0;
    
    			if ( direct )
    				alchemyBonus = (int)(from.Skills.Alchemy.Value / (Core.AOS ? 5 : 10));
    
    			IPooledEnumerable eable = LeveledExplosion ? map.GetObjectsInRange( loc, ExplosionRange ) : map.GetMobilesInRange( loc, ExplosionRange );
    			ArrayList toExplode = new ArrayList();
    
    			int toDamage = 0;
    
    			foreach ( object o in eable )
    			{
    				if ( o is Mobile )
    				{
    					toExplode.Add( o );
    					++toDamage;
    				}
    				else if ( o is BaseExplosionPotion && o != this )
    				{
    					toExplode.Add( o );
    				}
    			}
    
    			eable.Free();
    
    			int min = Scale( from, MinDamage );
    			int max = Scale( from, MaxDamage );
    
    			for ( int i = 0; i < toExplode.Count; ++i )
    			{
    				object o = toExplode[i];
    
    				if ( o is Mobile )
    				{
    					Mobile m = (Mobile)o;
    
    					if ( from == null || (SpellHelper.ValidIndirectTarget( from, m ) && from.CanBeHarmful( m, false )) )
    					{
    						if ( from != null )
    							from.DoHarmful( m );
    
    						int damage = Utility.RandomMinMax( min, max );
    
    						damage += alchemyBonus;
    
    						if ( !Core.AOS && damage > 40 )
    							damage = 40;
    						else if ( Core.AOS && toDamage > 2 )
    							damage /= toDamage - 1;
    
    						AOS.Damage( m, from, damage, 0, 100, 0, 0, 0 );
    					}
    				}
    				else if ( o is BaseExplosionPotion )
    				{
    					BaseExplosionPotion pot = (BaseExplosionPotion)o;
    
    					pot.Explode( from, false, pot.GetWorldLocation(), pot.Map );
    				}
    			}
    		}
    	}
    }
    
  2. Blaise

    Blaise Well-Known Member
    UO:R Subscriber

    Joined:
    Jul 14, 2012
    Messages:
    7,706
    Likes Received:
    3,632
    Can we just make them trigger each other and be volatile as well. If you're carrying them and get hit with the Explosion spell, they all explode, with the same area damage (so anyone in 2 tile radius gets hit too).

    Now that would make using epxplosion pots risky and exciting! :p

    Probably a bit too hardcore, but it would certainly keep people from carrying more than a few. :)
  3. DarkWing

    DarkWing Senior Counselor
    Senior Counselor
    UO:R Subscriber

    Joined:
    Jul 25, 2012
    Messages:
    345
    Likes Received:
    155

    I like this idea, Explo Potions are a bit unbalanced right now (no skill kills)
  4. Gideon Jura

    Gideon Jura Well-Known Member
    UO:R Donor

    Joined:
    Sep 8, 2012
    Messages:
    6,364
    Likes Received:
    5,579
    .
    Last edited: Jan 11, 2019
  5. Blaise

    Blaise Well-Known Member
    UO:R Subscriber

    Joined:
    Jul 14, 2012
    Messages:
    7,706
    Likes Received:
    3,632
    Watch everyone scatter when they see the numbers pop up!
    lol
  6. Chris

    Chris Renaissance Staff
    Renaissance Staff

    Joined:
    May 14, 2012
    Messages:
    3,385
    Likes Received:
    6,195
    This has been fixed on test center for about a week, and since this has been being abused recently we have moved up the next patch day to tomorrow morning.

    Also thanks for the code copy paste Tater, there is actually a standard last action timer bound to the mobile that is used for action delay, it is just rarely implemented in the runuo code (for reasons I cannot fathom). Rather than binding a timer to every potion use, we simply record the time in which another action can be attempted (any action).

    Code:
     if (DateTime.Now <= from.NextActionTime )
                {
                    from.SendMessage("You must wait before using another explosion potion.");
                    return;
                } 
                else ...
    
    And on the potion use
    Code:
                from.NextActionTime = DateTime.Now + TimeSpan.FromSeconds(1);
  7. Blaise

    Blaise Well-Known Member
    UO:R Subscriber

    Joined:
    Jul 14, 2012
    Messages:
    7,706
    Likes Received:
    3,632
    I'm curious, on the topic of Explo Pots, the relevance, or appropriateness of 'heat-seeking' explosion potions?
    How is that, if implemented, in any way functionally appropriate? It's a bottle, being thrown at someone. Does it contain some magical properties that allow it to 'follow' a target?


    It should land, where targetted, or where the player-target was when it was 'thrown', right?
  8. Tater Salad

    Tater Salad Renaissance Volunteers
    Renaissance Volunteers

    Joined:
    Nov 15, 2012
    Messages:
    312
    Likes Received:
    15
    Technically it always did land where the person was standing, it just looked like it was heat seeking, the damage check is applied when it blows up on the tile, regardless of how far the player is away from the tile after the potion "went off". The animation just tries to catch up.
  9. Blaise

    Blaise Well-Known Member
    UO:R Subscriber

    Joined:
    Jul 14, 2012
    Messages:
    7,706
    Likes Received:
    3,632
    The animation tries to catch up, but the damage won't, if the player is out of range of where they were originally targeted?
  10. Tater Salad

    Tater Salad Renaissance Volunteers
    Renaissance Volunteers

    Joined:
    Nov 15, 2012
    Messages:
    312
    Likes Received:
    15
    The way it should work is if thrown at the right time, right before exploding in your hand, it will explode damaging anything in the area of the tile you targeted, even if they are no longer on the tile when the potion animation lands. If you want to hop on test we can throw some pots around and check out how it works here.
  11. Chris

    Chris Renaissance Staff
    Renaissance Staff

    Joined:
    May 14, 2012
    Messages:
    3,385
    Likes Received:
    6,195
    Currently the potion tick is every 1 second for the detonate action, the animation is irrelevant from the damage aspect as it is handled on the targeting action. Currently it takes 2.75 seconds from use to detonation. With the tick check every 1 second after a .75 second delay.

    Whatever targets are in range on the 3rd tick of the timer, will take damage from the potion. The animation of the potion being thrown is simply based on whether the potion has exploded yet.

    When a location is targeted another timer is initialized on a 1 second tick check to determine where to execute the explosion. Whichever of these timers executes first in the 2.75 second range, will control the damage application and delete the potion.

    This is how I understand it at the moment, but I have only looked into this to resolve the action delay at the moment. I can create some kegs on test center this evening for anyone who wants to test it.
  12. Tater Salad

    Tater Salad Renaissance Volunteers
    Renaissance Volunteers

    Joined:
    Nov 15, 2012
    Messages:
    312
    Likes Received:
    15
    Well after some testing even if thrown split second before it still hits the tile and the person runs away unhurt.
  13. Blaise

    Blaise Well-Known Member
    UO:R Subscriber

    Joined:
    Jul 14, 2012
    Messages:
    7,706
    Likes Received:
    3,632
    Hm, and it should give damage if the person hasn't moved X amount of tiles from the targetted location?

    Yeah, I'll goof on test a bit later.
  14. Chris

    Chris Renaissance Staff
    Renaissance Staff

    Joined:
    May 14, 2012
    Messages:
    3,385
    Likes Received:
    6,195
    This was addressed in patch 31, I would be interested to know how everyone feels about the mechanic now. During pre patch testing everyone was pleased that the ability to hit a moving target is now based on your skill throwing the potion.
  15. Zagyg

    Zagyg Active Member
    UO:R Donor

    Joined:
    Aug 19, 2012
    Messages:
    507
    Likes Received:
    47
    Is it 'skill' throwing the potion, or just having the perfect macro for doing so? That's an honest question, not being sarcastic.

    I'm no expert on this, haven't tested it out extensively, but I was PKed last night by Axomanna and Bart and they used exp pots so I thought I'd share the experience. Maybe one of them would like to comment and explain it from their end in greater detail. I was at Fire Temple with reflect up, they ran on screen and bombarded me with something, dunno what, brought me down to 32 hp before I even started running. Then Bart killed me with one or more thrown explosion pots while I was at or close to a full run on horseback and greater than 6 tiles away from him. If it happened the way I think it happened, that I was caught with 2-3 exp pots and at least one while running fast through a forest, then I don't see how any non-PvP character could survive an attack like that. Unless I'm the only PvMer not carrying around Gheal pots and bandaids.
  16. Kryptonical

    Kryptonical Renaissance Volunteers
    Renaissance Volunteers

    Joined:
    Nov 23, 2012
    Messages:
    180
    Likes Received:
    50


    My crafters who never leave town, unless to recall to the house, have AT LEAST 5 Gheal and GCure pots on them :p and my macro to use both are very close to my recall macro
  17. Blaise

    Blaise Well-Known Member
    UO:R Subscriber

    Joined:
    Jul 14, 2012
    Messages:
    7,706
    Likes Received:
    3,632
    Now that's UO Prepared. My crafter has no way to heal at all. No reagents, no bandages, no potions. Hell, I don't even know if she has a spellbook.
  18. Zagyg

    Zagyg Active Member
    UO:R Donor

    Joined:
    Aug 19, 2012
    Messages:
    507
    Likes Received:
    47
    That's cool but a town crafter probably isn't as concerned with keeping his backpack weight low. I suppose it's not a big deal to make room for a few Gheal pots but it's kind of a moot point now. I got PKed by Ax and Bart again last night and I'm sure that a few pots wouldn't save me. I wouldn't last long enough after using the first one to ever use a second. It could not have taken any longer than 2 - 2.5 seconds for me to go from seeing them to dying from 100 health.

    [​IMG]
  19. Robbbb

    Robbbb Member

    Joined:
    Aug 20, 2012
    Messages:
    106
    Likes Received:
    0

    From your journal, had you had your recall setup on a macro, you would have gotten away...
  20. Zagyg

    Zagyg Active Member
    UO:R Donor

    Joined:
    Aug 19, 2012
    Messages:
    507
    Likes Received:
    47
    Don't want to completely derail the topic, but it shows in the journal Playing macro "recall" and Macro "recall" finished. I ran a few steps (pretty sure that's the 'you stop meditating' part) and hit my recall macro key. It actually went off but I still died at my destination.

Share This Page