Jack submitted a new resource:

OSI Accurate Bounty System - A drag-drop compatible, OSI accurate bounty system.

This is an OSI accurate bounty system that was disabled with publish 16, June 2002, which coincided with the removal of stat loss for player killers. It is completely drag drop compatible (tested versions: RunUO 2.2, RunUO 2.5, ServUO current). It is compatible with the account gold system that was added to ServUO recently.

When I was created my own shard I was astounded that this system did not exist by default, and no-one had ever released it. As a newbie coder it took me a lot of...

Read more about this resource...
 
Wow! Thank you so much @Jack for sharing this!
This bounty system is very notorious.(bad pun) I'm sure people will find this resource very useful.
 
Cheers! It took me some time and thought to get this to be fully drag drop compatible. Hope it serves the community well.
 
Hey @Jack - thanks for this resource. We've implemented on our development server and will be rolling it out soon!

There are a couple of issues though (but easily fixable)!

1) You're checking for whether or not the new banking system has been activated by checking for the ServUO tag. You shouldn't do this, because some people might be using ServUO but NOT the new banking system. I'd suggest using something like AccountGold.Enabled - which is defined in the CurrentExpansion.cs file. This is the "switch" that needs to be flipped to turn on the new banking system.

2) The second major issue is what happens when someone posts a bounty of more money than what they have - the Withdraw function runs and regardless of whether or not it succeeded, the bounty goes through. Essentially, if someone only has 500k gold and posts a bounty of 1mil, then no gold will be withdrawn from their account and there will be a bounty of 1mil gold placed! To solve this, put the check for whether or not a bounty was input and whether or not the Withdraw was successful at the beginning of the OnResponse from the gump (BEFORE you even add a murder count). This will allow you to reprompt the player with a gump if the input is wrong.

That's about it! Everything else seems to work fine. Thanks so much for putting in so much legwork!
 
Hey @Jack - thanks for this resource. We've implemented on our development server and will be rolling it out soon!

There are a couple of issues though (but easily fixable)!

1) You're checking for whether or not the new banking system has been activated by checking for the ServUO tag. You shouldn't do this, because some people might be using ServUO but NOT the new banking system. I'd suggest using something like AccountGold.Enabled - which is defined in the CurrentExpansion.cs file. This is the "switch" that needs to be flipped to turn on the new banking system.

2) The second major issue is what happens when someone posts a bounty of more money than what they have - the Withdraw function runs and regardless of whether or not it succeeded, the bounty goes through. Essentially, if someone only has 500k gold and posts a bounty of 1mil, then no gold will be withdrawn from their account and there will be a bounty of 1mil gold placed! To solve this, put the check for whether or not a bounty was input and whether or not the Withdraw was successful at the beginning of the OnResponse from the gump (BEFORE you even add a murder count). This will allow you to reprompt the player with a gump if the input is wrong.

That's about it! Everything else seems to work fine. Thanks so much for putting in so much legwork!

1) Don't have time to look right now but I have a question - from what I recall there isn't any check for what system is being used at all. It just uses the banker deposit and banker withdraw functions which do the account gold checks themselves don't they? Admittedly making this ServUO compatible was an afterthought and I didn't spend much time looking into it, but my understanding was that the withdraw/deposit functions take care of this. I did however test that both AccountGold and non AccountGold worked to bounty/collect.

2) This is a serious issue and I will fix this tonight. It was introduced when I did some last minute refactoring for ServUO and forgot to cap the bounty at the amount of gold in the bank.
 
Last edited:
The problem still exists, from what I can see.

Code:
killer.Kills++;
killer.ShortTermMurders++;

.......

((PlayerMobile) from).RecentlyReported.Add(killer);
		if (bounty > 0)
		{
#if ServUO
			Banker.Withdraw(from, bounty);
#else
			RemoveGoldFromBank(from, bounty);
#endif
			BountyInformation.AddBounty(pk, bounty, true);

			pk.SendMessage("{0} has placed a bounty of {1} {2} on your head!", from.Name,
				bounty, bounty == 1 ? "gold piece" : "gold pieces");
			pk.Say(500546); // I am now bounty hunted!

			from.SendMessage("You place a bounty of {0}gp on {1}'s head.", bounty, pk.Name);
		}

You're processing the murder count BEFORE you're checking whether or not they have the right amount of money in their account. Moreover - you're not even checking if they have the right amount of money. "Banker.Withdraw" will TRY to remove the set amount - but if it can't, then it will fail, but the bounty will still be placed. (If you input more than what's in your bank account, your RemoveGoldFromBank function will eat through ALL the gold in the bank before continuing on and placing the original bounty).

This can all be easily fixed (assuming you're using the Banker.Withdraw function).

Code:
if (bounty > 0)
{
	if (Banker.Withdraw(from, bounty))
   {
			BountyInformation.AddBounty(pk, bounty, true);

			pk.SendMessage("{0} has placed a bounty of {1} {2} on your head!", from.Name,
				bounty, bounty == 1 ? "gold piece" : "gold pieces");
			pk.Say(500546); // I am now bounty hunted!

			from.SendMessage("You place a bounty of {0}gp on {1}'s head.", bounty, pk.Name);
   } else {
		   from.SendMessage("You do not have that much gold in your bank!");
		   from.SendGump(new ReportMurdererBountyGump(from, _killers, _idx));
		   return;
   }
}

You also need to place this code BEFORE you increment the murder counts. That way the user will keep getting prompted until they put in a valid amount.
 
As for your custom gold eating function. I'd make it search through the bankbox NOT eating gold, but just checking if there is enough gold in there before searching through the bankbox again and eating the gold. I'd also switch it from a void function to a bool function so you can return whether or not the gold has been consumed and you can put it in an if statement the same way I did in the above code.
 
Hmm, did you download the latest version?

The code you pasted seems to be missing this:

Code:
     var bounty = Math.Min( Utility.ToInt32(c.Text), GetBountyMax( from ) );
								
     if (bounty > 0)
     {

The bounty is now calculated as a minimum of what the person entered vs what the actual amount of gold in the bank is.

Because of this, when we actually go to withdraw, we know that the bank has that much gold in it. If the person entered 1,000,000 gold and they only have 30 gold in their bank, then the var "bounty" will hold 30 in it. Correct me if I'm wrong but I cannot see any situation in which Banker.Withdraw(from, bounty); or RemoveGoldFromBank(from, bounty); would withdraw anything but either what the person entered, or the total amount of gold in the bank.

Kills are added regardless of whether the person entered a bounty or not, and a bounty is only applied if the person has gold in their bank.
 
Ok, so that checks out functionally. But what occurs when a typo happens? Someone enters 1000 when they only have 100 will result in no bounty being created. As a player, I'd be confused if that happened. Obviously I want to create a bounty, but because I entered too much money in there, a bounty is not being created.

Call it being a stickler for best-practice. But Banker.Withdraw already checks if you have enough money within it. You're creating all these redundant functionalities that are already present in other parts of the codebase. I would sanitise the amount input and then just stick it in the Banker.Withdraw which returns a bool.

If, for whatever reason, you want to change the way that gold is calculated in an account on the server - if you're using your script, you're going to have to not only change the code inside Banker but also find the snippet of code inside the bounty system and change that. Also, if this is a coding habit and you're creating more and more systems all with their own checks for whether or not you have money, then you're going around plugging holes rather than just changing it from one location.

There's more than one right answer for coding. It's just... some correct answers are better than other correct answers ;)
 
Ok, so that checks out functionally. But what occurs when a typo happens? Someone enters 1000 when they only have 100 will result in no bounty being created. As a player, I'd be confused if that happened. Obviously I want to create a bounty, but because I entered too much money in there, a bounty is not being created.
Well this isn't actually true, it would create a 100gp bounty in that situation.

You are right that these should use the banker balance/withdraw/deposit functions, that code itself was written a long time ago when I was new to coding and for whatever reason at the time I didn't think such a function existed in Banker.cs. On review even RunUO 2.2 contains a bool function for deposit/withdraw.
 
So I just installed this on a pre aos shard.

Newest SVN

Two issues

1. the bounty guards disappear like normal guards would after a few seconds of being spawned. (can fix that myself)

2. had a player kill another player, player was not prompted to enter a bounty.
 
Last edited:
I wonder if something changed with guards, I'll test this out with the newest version of ServUO when I get some free time.
 
I haven't been spending a lot of time with UO lately and the time I do spend goes into managing my shard, so I haven't gotten around to a fix for this. I took a quick look into why the bounty guards disappear and it's due to the idle timer in WarriorGuard.

This isn't a particularly easy fix while making the guards ServUO drag drop compatible and would probably have to be a bit hacky if I were to keep using WarriorGuard as a base class.

The easiest solution would probably be to NOT use WarriorGuard as a base class and just have the BountyGuard be a BaseCreature.
 
I haven't been spending a lot of time with UO lately and the time I do spend goes into managing my shard, so I haven't gotten around to a fix for this. I took a quick look into why the bounty guards disappear and it's due to the idle timer in WarriorGuard.

This isn't a particularly easy fix while making the guards ServUO drag drop compatible and would probably have to be a bit hacky if I were to keep using WarriorGuard as a base class.

The easiest solution would probably be to NOT use WarriorGuard as a base class and just have the BountyGuard be a BaseCreature.

I just turned it into an orderguard
 
Hi Jack, thanks for this!

I'm having a problem that the pop up bounty gump does not give me the opportunity to enter an amount. Just has the red and green buttons to report or not.

Also on nailing the test toon another couple of times he did not get the opportunity even to report. So I mean he got no gump at all.

Using RunUO 2.4

Thanks

David
 
Is there any chance we can add a temp stat loss to the head turn in on this. Like a 48 hour stat loss?
 
Apologies for the lack of reply, I haven't spent much time on these forums lately. I'll update this (hopefully) tomorrow to fix the problem with guards disappearing.

I'm having a problem that the pop up bounty gump does not give me the opportunity to enter an amount. Just has the red and green buttons to report or not.
You can only apply a bounty if the character who killed you is at 4 or more murder counts.

Also on nailing the test toon another couple of times he did not get the opportunity even to report. So I mean he got no gump at all.
You may only report a character once every 10 minutes for murder under this system. Could that be the reason?

Is there any chance we can add a temp stat loss to the head turn in on this. Like a 48 hour stat loss?
This is designed to be an OSI accurate system, not a custom one. If you want to do that, I'd suggest putting a trigger into BountyGuard.cs OnDragDrop, and looking into how the faction system deals with temporary stat loss.
 
Just installed! Works flawlessly! Thank you very much for the hard work that you have placed into this! the guard(s) do despawn rather quickly but easily fixed on my end.
 
BountyGuard.cs
Code:
using Server.Items;
using Server.Misc;
using System;
using System.Collections.Generic;

namespace Server.Mobiles
{
    public class BountyGuard : BaseCreature
    {
		public override bool ClickTitle { get { return true; } }
        [Constructable]
        public BountyGuard() : base( AIType.AI_Melee, FightMode.Aggressor, 10, 1, 0.45, 0.8 )
        {
			InitStats( 1000, 1000, 1000 );
			Title = "the bounty hunter";

			SpeechHue = Utility.RandomDyedHue();

			Hue = Utility.RandomSkinHue();

			if ( Female = Utility.RandomBool() )
			{
				Body = 0x191;
				Name = NameList.RandomName( "female" );

				switch( Utility.Random( 2 ) )
				{
					case 0: AddItem( new LeatherSkirt() ); break;
					case 1: AddItem( new LeatherShorts() ); break;
				}

				switch( Utility.Random( 5 ) )
				{
					case 0: AddItem( new FemaleLeatherChest() ); break;
					case 1: AddItem( new FemaleStuddedChest() ); break;
					case 2: AddItem( new LeatherBustierArms() ); break;
					case 3: AddItem( new StuddedBustierArms() ); break;
					case 4: AddItem( new FemalePlateChest() ); break;
				}
			}
			else
			{
				Body = 0x190;
				Name = NameList.RandomName( "male" );

				AddItem( new PlateChest() );
				AddItem( new PlateArms() );
				AddItem( new PlateLegs() );

				switch( Utility.Random( 3 ) )
				{
					case 0: AddItem( new Doublet( Utility.RandomNondyedHue() ) ); break;
					case 1: AddItem( new Tunic( Utility.RandomNondyedHue() ) ); break;
					case 2: AddItem( new BodySash( Utility.RandomNondyedHue() ) ); break;
				}
			}
			Utility.AssignRandomHair( this );
			
			if( Utility.RandomBool() )
				Utility.AssignRandomFacialHair( this, HairHue );

			Halberd weapon = new Halberd();

			weapon.Movable = false;
			weapon.Crafter = this;
			weapon.Quality = WeaponQuality.Exceptional;

			AddItem( weapon );
     
			Container pack = new Backpack();

			pack.Movable = false;

			pack.DropItem( new Gold( 10, 25 ) );

			AddItem( pack );

			Skills[SkillName.Anatomy].Base = 120.0;
			Skills[SkillName.Tactics].Base = 250.0;
			Skills[SkillName.Swords].Base = 120.0;
			Skills[SkillName.MagicResist].Base = 120.0;
			Skills[SkillName.DetectHidden].Base = 100.0;

            //this.NextCombatTime = Core.TickCount + 500;
			//this.Focus = target;
		}

        private static readonly List<int> RejectedHeadSayings = new List<int>()
        {
            500661, // I shall place this on my mantle!
            500662, // This tasteth like chicken.
            500663, // This tasteth just like the juicy peach I just ate.
            500664, // Ahh!  That was the one piece I was missing!
            500665, // Somehow, it reminds me of mother.
            500666, // It's a sign!  I can see Elvis in this!
            500667, // Thanks, I was missing mine.
            500668, // I'll put this in the lost-and-found box.
            500669 // My family will eat well tonight!
        };

        public override bool OnDragDrop(Mobile from, Item dropped)
        {
            var bh = dropped as BountiedHead;
            if (bh != null && bh.Player != null && !bh.Player.Deleted &&
                bh.Created + TimeSpan.FromDays(1.0) > DateTime.UtcNow)
            {
                SayTo(from, 500670); // Ah, a head!  Let me check to see if there is a bounty on this.
                Timer.DelayCall(TimeSpan.FromSeconds(3.0), CheckBountyOnHead, new object[] {bh, from});
                return true;
            }
            else if (dropped is Head)
            {
                Say(RejectedHeadSayings[Utility.Random(RejectedHeadSayings.Count)]);
                //dropped.Delete();
                //return true;
            }

            SayTo(from, 500660); // If this were the head of a murderer, I would check for a bounty.
            return false;
        }

        private void CheckBountyOnHead(object[] states)
        {
            var head = (BountiedHead) states[0];
            var bountyHunter = (Mobile) states[1];
            var bi = BountyInformation.GetBountyInformation(head.Player);

            if (bi == null)
            {
                Say("The reward on this scoundrel's head has already been claimed!");
                head.Delete();
                return;
            }

            var totalBounty = bi.Bounty;
            var headBounty = head.Bounty;
            var difference = totalBounty - headBounty;

            if (difference < 0)
            {
                Say("The reward on this scoundrel's head has already been claimed!");
                head.Delete();
                return;
            }

            bi.SubtractBounty(headBounty);
            AwardBounty(bountyHunter, head.PlayerName, headBounty);
            head.Delete();
        }

        private void AwardBounty(Mobile bountyHunter, string killer, int total)
        {
            var bountySize = 0;

            if (total > 15000)
                bountySize = 2;
            else if (total > 100)
                bountySize = 1;

            switch (bountySize)
            {
                case 2:
                    bountyHunter.PlaySound(0x2E6);
                    Say("Thou hast brought the infamous " + killer + " to justice!  Thy reward of " + total + "gp hath been deposited in thy bank account.");
                    break;
                case 1:
                    bountyHunter.PlaySound(0x2E5);
                    Say("Tis a minor criminal, thank thee. Thy reward of " + total + "gp hath been deposited in thy bank account.");
                    break;
                default:
                    bountyHunter.PlaySound(0x2E4);
                    Say("Thou hast wasted thy time for " + total + "gp.");
                    break;
            }

            Banker.Deposit(bountyHunter, total);
        }

        public BountyGuard(Serial serial) : base(serial)
        {
        }

        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);

            writer.Write(0); // version
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);

            var version = reader.ReadInt();
        }
    }
}
This is fixing the fast respawn .. the bounty hunter stay here, can made him invulnerable , but he have InitStats( 1000, 1000, 1000 ); i dont think anyone want try him.
fix when not the good head is not deleted > must fix the RejectedHeadSayings maby juste say no bounty on this head.
 
RunUO - [www.runuo.com] Version 2.2, Build 4782.3756
Core: Running on .NET Framework Version 2.0.50727
Core: Optimizing for 2 processors
Scripts: Compiling C# scripts...failed (1 errors, 0 warnings)
Errors:
+ News Script/Bounties Recompensa/BountyGuard.cs:
CS1002: Line 15: Se esperaba ;
CS1519: Line 16: El símbolo '{' no es válido en una clase, estructura o decl
aración de miembro de interfaz
CS1518: Line 28: Se esperaba una clase, un delegado, una enumeración, una in
terfaz o una estructura
CS1518: Line 35: Se esperaba una clase, un delegado, una enumeración, una in
terfaz o una estructura
CS1001: Line 35: Se esperaba un identificador
CS1518: Line 35: Se esperaba una clase, un delegado, una enumeración, una in
terfaz o una estructura
CS0116: Line 35: Un espacio de nombres no contiene directamente miembros com
o campos o métodos
CS1022: Line 37: Se esperaba una definición de tipo o espacio de nombres, o
el fin del archivo
Scripts: One or more scripts failed to compile or no script files were found.
- Press return to exit, or R to try again.
 
Unfortunately, the enhanced client does not display the bounty board content. Will try to fix it :p
 
Last edited:
Back