Status
Not open for further replies.
Can't believe I didn't know this was out sooner. Thanks, Spectre. This is really great work!
Thank you, but I can only take credit for the changes I made since posting this resource. Everything before it was Djeryv's efforts. Some of the changes I did make were errors found by others, and some of them already had a fix so I would implement those. Like the saw mills and boat sailing crashes.
 
Last edited:
Thank you, but I can only take credit for the changes I made since posting this resource. Everything before it was Djeryv's efforts. Some of the changes I did make were errors found by others, and some of them already had a fix so I would implement those. Like the saw mills and boat sailing crashes.
I know. I'm Nephtan from the reddit posts. The updates and fixes you've made are wonderful. I've been comparing it to the last R&R update and also to my own changes and will be merging in a lot of your work. I really appreciate it!
 
I've been comparing it to the last R&R update and also to my own changes and will be merging in a lot of your work.
I don't have a Reddit account, but I do read the stuff there to see if people find problems or have ideas. Someone posted that they wanted to make some buff icons so I went ahead and did as many icons as I could find effects for in the game. If you decide to grab that, it will be a little bit laborious. You have to grab the Gump IDs that I created/set. Then you have to grab the Cliloc entries I added. Lastly, all of the places in scripts where I call a buff icon to appear.

I didn't know what Gump IDs I could use for buff icons, since UO does not have them in any order. I just looked at the ClassicUO source code to see the hex numbers for the gumps to icons. It was an annoying process, but worth it.

I have to say that Reddit is a funny place. I remember seeing some post asking about what to make or what to do, by someone stating that they never played UO before. Then I looked at their post history on Reddit, only to see them posting a bunch of things in the r/ultimaonline sub about playing other servers.
 
I don't have a Reddit account, but I do read the stuff there to see if people find problems or have ideas. Someone posted that they wanted to make some buff icons so I went ahead and did as many icons as I could find effects for in the game. If you decide to grab that, it will be a little bit laborious. You have to grab the Gump IDs that I created/set. Then you have to grab the Cliloc entries I added. Lastly, all of the places in scripts where I call a buff icon to appear.

I didn't know what Gump IDs I could use for buff icons, since UO does not have them in any order. I just looked at the ClassicUO source code to see the hex numbers for the gumps to icons. It was an annoying process, but worth it.
Thanks for those tips. I haven't made any of my own changes to many of those files so it's pretty easy for me to merge them in, it's just a lot to go through.
I have to say that Reddit is a funny place. I remember seeing some post asking about what to make or what to do, by someone stating that they never played UO before. Then I looked at their post history on Reddit, only to see them posting a bunch of things in the r/ultimaonline sub about playing other servers.
I try not to look too much these days. Face forward, find what I need, and get out.
 
Possible bug? The painter in the Britain castle doesn't accept any of the paintings I've found in TMap chests. The art collector takes them just fine. Or do I misunderstand what the painter is for?
 
Yeah, the Portraits of Adventurers conversation, second paragraph says he pays double for paintings what the art collector would
 
So I think I may have found it, but Im not sure.. I was looking through all the files, and i found something in SavageRegion.cs
1707855905821.png
Kinda sounds like PvP to me, but im a novice.. So I looked in ProtectedRegion.cs
1707855966024.png
And found this.. So do I have to add/modify this line for every region I dont want PvP? or can I just add it to BaseRegion.cs, and be done with it? Thanks in advance! ;)
 
Which one(s)? I have been to an ankh, ancient dryad, and buddha one and they display fine.
Shoot, I've seen two so I assumed it was all of them. I will note the locations as I see them again
1707861053600.pngMoon, Britain, and Montor so far. maybe its just the ones in the cities?
 
Last edited:
same deal, I may have tweaked something by doing the pause thing. It still works, and I only noticed by accident. could be just me

I found the issue. The SERVER package was not fully updated by me. I forgot the new WORLD.EXE file. I fixed that file if you want to redownload it, replace the WORLD.EXE file, then when you get into the game do a [buildworld. Was in a rush that morning I suppose.
 
I found the issue. The SERVER package was not fully updated by me. I forgot the new WORLD.EXE file. I fixed that file if you want to redownload it, replace the WORLD.EXE file, then when you get into the game do a [buildworld. Was in a rush that morning I suppose.
That did the trick, I'll try to be more detailed when or if I find anything else out of place
 
Spectre - thanks for keeping the Ruins and Riches dream alive! You truly are the ghost of Djeryv.

I've been trying to merge in bugfixes locally as I've come across them and add a few other things that don't mess with the R&R flavor.

Here are my contributions -

Fix game client music on Linux / case-sensitive file systems
Rename the Game/Files/music folder to Game/Files/Music.

Fix IP address mapping for NAT'd networks
port of this PR (ServerListMap: ServerList selection by matching CIDR of remote address by ptgeorge · Pull Request #4955 · ServUO/ServUO) to RunUO:

Data/System/CFG/Assemblies.cfg:
System.dll
System.Web.dll
System.Xml.dll
System.Data.dll
System.Drawing.dll
System.Windows.Forms.dll
System.Core.dll

Data/Scripts/System/Misc/ServerList.cs:
#region References
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Xml;
using Server.Network;

#endregion

namespace Server.Misc
{
    public class ServerList
    {
        /*
        * The default setting for Address, a value of 'null', will use your local IP address. If all of your local IP addresses
        * are private network addresses and AutoDetect is 'true' then ServUO will attempt to discover your public IP address
        * for you automatically.
        *
        * If you do not plan on allowing clients outside of your LAN to connect, you can set AutoDetect to 'false' and leave
        * Address set to 'null'.
        *
        * If your public IP address cannot be determined, you must change the value of Address to your public IP address
        * manually to allow clients outside of your LAN to connect to your server. Address can be either an IP address or
        * a hostname that will be resolved when ServUO starts.
        *
        * If you want players outside your LAN to be able to connect to your server and you are behind a router, you must also
        * forward TCP port 2593 to your private IP address. The procedure for doing this varies by manufacturer but generally
        * involves configuration of the router through your web browser.
        *
        * ServerList will direct connecting clients depending on both the address they are connecting from and the address and
        * port they are connecting to. If it is determined that both ends of a connection are private IP addresses, ServerList
        * will direct the client to the local private IP address. If a client is connecting to a local public IP address, they
        * will be directed to whichever address and port they initially connected to. This allows multihomed servers to function
        * properly and fully supports listening on multiple ports. If a client with a public IP address is connecting to a
        * locally private address, the server will direct the client to either the AutoDetected IP address or the manually entered
        * IP address or hostname, whichever is applicable. Loopback clients will be directed to loopback.
        *
        * If you would like to listen on additional ports (i.e. 22, 23, 80, for clients behind highly restrictive egress
        * firewalls) or specific IP adddresses you can do so by modifying the file SocketOptions.cs found in this directory.
        */

        // For R&R, put the file in the Info dir, with the other settings files
        private static readonly string m_AddressMapConfigPath = Path.Combine("Info", "ServerListMap.xml");

        // Triple of CIDR pattern (IPAddress/Length) to server listing address
        private static List<(IPAddress, int, IPAddress)> _AddressMap = new List<(IPAddress, int, IPAddress)>();

        // DEFAULT: public static readonly string Address = Config.Get("Server.Address", default(string));
        public static readonly string Address = null;

        // DEFAULT: public static readonly bool AutoDetect = Config.Get("Server.AutoDetect", true);
        public static readonly bool AutoDetect = false;

                public static string ServerName = MyServerSettings.ServerName();

        private static IPAddress _PublicAddress;

        private static readonly Regex _AddressPattern = new Regex(@"([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})");

        public static void Initialize()
        {
            LoadAddressMap(m_AddressMapConfigPath);

            if (Address == null)
            {
                if (AutoDetect)
                {
                    AutoDetection();
                }
            }
            else
            {
                Resolve(Address, out _PublicAddress);
            }

            EventSink.ServerList += EventSink_ServerList;
        }

        private static void LoadAddressMap(string ConfigFile)
        {
            if (!File.Exists(ConfigFile)) {
                Console.WriteLine("ServerListMap: Not present @ {0}", ConfigFile);
                return;
            }

            XmlDocument doc = new XmlDocument();
            doc.Load(ConfigFile);
            foreach (XmlNode node in doc.GetElementsByTagName("serverListMap")[0].ChildNodes)
            {
                if (node.Name.Equals("entry") && node.Attributes["cidrmatch"] != null && node.Attributes["destination"] != null)
                {
                    var cidrmatch = node.Attributes["cidrmatch"].Value;
                    // This uses the added function at the EOF.
                    TryParseCIDR(cidrmatch, out var cidrAddress, out var cidrLength);

                    IPAddress net;
                    try
                    {
                        IPAddress.TryParse(cidrmatch, out net);
                    }
                    catch
                    {
                        Console.WriteLine("ServerListMap: Invalid CIDR {0} - XML: {1}", cidrmatch, node.OuterXml);
                        continue;
                    }

                    var destination = node.Attributes["destination"].Value;
                    IPAddress dest;
                    try
                    {
                            IPAddress.TryParse(destination, out dest);
                    }
                    catch
                    {
                            Console.WriteLine("ServerListMap: Invalid Destination {0} - XML: {1}", destination, node.OuterXml);
                            continue;
                    }

                    _AddressMap.Add((cidrAddress, cidrLength, dest));

                    Console.WriteLine("ServerListMap: {0} => {1}", cidrmatch, dest);
                }
                else if (node.NodeType != XmlNodeType.Comment)
                        Console.WriteLine("ServerListMap: XML does not match required specification - {0}", node.OuterXml);
            }
        }

        private static void EventSink_ServerList(ServerListEventArgs e)
        {
            try
            {
                NetState ns = e.State;
                Socket s = ns.Socket;

                IPEndPoint ipep = (IPEndPoint)s.LocalEndPoint;

                IPAddress localAddress = ipep.Address;
                int localPort = ipep.Port;

                IPEndPoint ipepRemote = (IPEndPoint)s.RemoteEndPoint;

                if (IsPrivateNetwork(localAddress))
                {
                    if (!IsPrivateNetwork(ipepRemote.Address) && _PublicAddress != null)
                    {
                        localAddress = _PublicAddress;
                    }
                }

                // ServerListMap matches
                // NOTE: original - foreach (var (address, length, destination) in _AddressMap)
                // This form of tuple deconstruction does not appear to work due to Mono/.NET version used here.
                foreach (var i in _AddressMap)
                {
                    // Pull data from our Tuple due to the lack of nicer foreach deconstruction
                    var address = i.Item1;
                    var length = i.Item2;
                    var destination = i.Item3;


                    if (Utility.IPMatchCIDR(address, ipepRemote.Address, length))
                    {
                        localAddress = destination;
                        break;
                    }
                }

                e.AddServer(ServerName, new IPEndPoint(localAddress, localPort));
            }
            catch
            {
                e.Rejected = true;
            }
        }

        public static string[] IPServices =
        {
            "http://services.servuo.com/ip.php", "http://api.ipify.org",
            "http://checkip.dyndns.org/"
        };

        private static void AutoDetection()
        {
            if (!HasPublicIPAddress())
            {
                Utility.PushColor(ConsoleColor.Yellow);
                Console.WriteLine("ServerList: Auto-detecting public IP address...");

                _PublicAddress = FindPublicAddress(IPServices);

                if (_PublicAddress != null)
                {
                    Console.WriteLine("ServerList: Done: '{0}'", _PublicAddress);
                }
                else
                {
                    _PublicAddress = IPAddress.Any;

                    Console.WriteLine("ServerList: Failed: reverting to private IP address...");
                }

                Utility.PopColor();
            }
        }

        private static void Resolve(string addr, out IPAddress outValue)
        {
            if (IPAddress.TryParse(addr, out outValue))
            {
                return;
            }

            try
            {
                IPHostEntry iphe = Dns.GetHostEntry(addr);

                if (iphe.AddressList.Length > 0)
                {
                    outValue = iphe.AddressList[iphe.AddressList.Length - 1];
                }
            }
            catch(Exception e)
            {
                // Diagnostics.ExceptionLogging.LogException(e);
                Console.WriteLine(e);
            }
        }

        private static bool HasPublicIPAddress()
        {
            NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();
            System.Collections.Generic.IEnumerable<IPAddress> uips = adapters.Select(a => a.GetIPProperties())
                               .SelectMany(p => p.UnicastAddresses.Cast<IPAddressInformation>(), (p, u) => u.Address);

            return
                uips.Any(
                    ip => !IPAddress.IsLoopback(ip) && ip.AddressFamily != AddressFamily.InterNetworkV6 && !IsPrivateNetwork(ip));
        }

        private static bool IsPrivateNetwork(IPAddress ip)
        {
            // 10.0.0.0/8
            // 172.16.0.0/12
            // 192.168.0.0/16
            // 169.254.0.0/16
            // 100.64.0.0/10 RFC 6598

            if (ip.AddressFamily == AddressFamily.InterNetworkV6)
            {
                return false;
            }

            if (Utility.IPMatch("192.168.*", ip))
            {
                return true;
            }

            if (Utility.IPMatch("10.*", ip))
            {
                return true;
            }

            if (Utility.IPMatch("172.16-31.*", ip))
            {
                return true;
            }

            if (Utility.IPMatch("169.254.*", ip))
            {
                return true;
            }

            if (Utility.IPMatch("100.64-127.*", ip))
            {
                return true;
            }

            return false;
        }

        public static IPAddress FindPublicAddress(params string[] services)
        {
            if (services == null || services.Length == 0)
            {
                services = IPServices;
            }

            if (services == null || services.Length == 0)
            {
                return null;
            }

            IPAddress ip = null;

            Uri uri;
            string data;
            Match match;

            foreach (string service in services.Where(s => !string.IsNullOrWhiteSpace(s)))
            {
                try
                {
                    uri = new Uri(service);

                    Console.WriteLine("ServerList: >>> {0}", uri.Host);

                    using (WebClient client = new WebClient())
                    {
                        data = client.DownloadString(uri);
                    }

                    Console.WriteLine("ServerList: <<< {0}", data);

                    match = _AddressPattern.Match(data);

                    if (!match.Success || !IPAddress.TryParse(match.Value, out ip))
                    {
                        ip = null;
                    }
                }
                catch (UriFormatException)
                {
                    Console.WriteLine("ServerList: Invalid IP service Uri '{0}'", service);

                    ip = null;
                }
                catch
                {
                }

                if (ip != null)
                {
                    break;
                }
            }

            return ip;
        }

       //Added from the Utility file included with the patch
                public static bool TryParseCIDR(string cidr, out IPAddress cidrAddress, out int length)
                {
                        var cidrParts = cidr.Split('/');
                        try
                        {
                                IPAddress.TryParse(cidrParts[0], out cidrAddress);
                                Int32.TryParse(cidrParts[1], out length);
                                return true;
                        }
                        catch
                        {
                                cidrAddress = null;
                                length = 0;
                                return false;
                        }
                }
    }
}

Info/ServerListMap.xml:
<?xml version="1.0" encoding="UTF-8"?>
<serverListMap>
    <!-- entry elements define a remote cidr address match that maps to a destination
    cidrmatch   String  IP CIDR to match against the remote address, such as 192.168.1.0/24
      Input the containers address/network in CIDR format
    destination String  Destination IP to use if matched
      Input the world accessible "physical" server IP address
    -->
    <!-- Example 1 - Single address match where NATing is insufficient / misconfigued (SSH tunnel + docker)
    <entry cidrmatch="172.18.0.1/32" destination="127.0.0.1"/>
    -->
    <!-- Example 2 - Local network match where NAT'ing is insufficent / misconfigured (docker)
    <entry cidrmatch="192.168.1.0/24" destination="192.168.1.100"/>
    -->
    <!-- Example 3 - Local network match where NAT'ing is insufficent / misconfigured (podman default network)
    <entry cidrmatch="10.89.0.0/16" destination="192.168.1.100"/>
    -->
</serverListMap>
 
Some recent things I've noticed are:
Gold and Gold Coins are now a thing, GC seems to be what I am getting now, but it all spends the same so I've been trying to just cycle out the Gold.
Same thing with Bandages are now being made as Clean Bandages,
Neither of the two same-thing-items stack together but are useable, nonetheless.

Docking Lantern... Dont know how they are supposed to work, but once I set mine down on the ground outside my house steps, its stuck. Can't pick it up, unlock or unsecure it, or even use it. Even with admin, can't remove it or anything...

I tried re-updating with the newest world and client and same. I'm continuing this game because I can ignore most of what I'm encountering, but if my game sounds like it needs to be scrapped and started over, let me know

Edit: I went ahead and started a fresh install and game. The money and bandages are all one kind now, though I’ve noticed the bank vendor (minter?) is selling no-name deeds

Edit: Bottle from thief guildmaster is the Hair Dye Bottle. Want me to keep listing them or are they all connected?
 
Last edited:
Oh, another thing. I don't think its a bug, just overlooked, but can you rename oil cloths so that when i am cutting them down, my macro doesn't cut my folded cloths?
 
Spectre updated Adventurers of Akalabeth with a new update entry:

Beggar - 19 February 2024

This is an important (yet small) update and it is both a CLIENT and WORLD/SERVER update. Replace your World.exe.

- More item naming fixes, requiring a World.exe update.
- Added some new monsters.
- Fixed some spell references to incorrect skills.

A note on the naming issues recently:

Djeryv has a bit of a unique magic/item naming system, where he attempted to capture the names of items so those names could be changed dynamically during play. The dynamic naming means he couldn't rely on...

Read the rest of this update entry...
 
My old gold and new gold didn’t mix, but I was able to spend the old stuff away and everything else is all good so far. Out of curiosity, all my stolen containers seem to have lost their randomness in regards to value (100, 200, 300, etc) is that a coincidence?
1708497930347.png
Happened while transferring loot between characters, nowhere in particular, i think i had just chugged a bunch of unidentified potions
 
Last edited:
My old gold and new gold didn’t mix, but I was able to spend the old stuff away and everything else is all good so far.
Strange as the deserialize should have taken care of this but until I see this unmixed gold I can't really address it.
Out of curiosity, all my stolen containers seem to have lost their randomness in regards to value (100, 200, 300, etc) is that a coincidence?
Never messed with that code and your description of the issue isn't really that clearly written.
Happened while transferring loot between characters, nowhere in particular, i think i had just chugged a bunch of unidentified potions
For some reason, your online.txt file (in INFO dir) was locked and so the server crashed. I can maybe check into this and see if it has the same safety catch as the other txt files stored there.
 
Strange as the deserialize should have taken care of this but until I see this unmixed gold I can't really address it
I had the same issue, i dropped 2 type of currency. Checking with the admin account the only difference between the "gold" and "gold coin" is on the name properety of the item. The vendors and monster drops give the "gold coin" version of currency. So somewhere there is a piece of code that give the name "gold" to the currency. Im trying to replicate the issue again to check the "gold" one come from.

There is a similar issue with potions, there is the Total Refreshment Potion and Total Refresh Potion.
 
Last edited:
Its the same gold.cs file i have. Ive noticed another thing, silver and copper get the plural notation, gold dont its always "gold coin". Dunno what u have modified, im just trying to giving u a clue
Ugh. It keeps grabbing an old file on upload.
 

Attachments

  • Gold.cs
    1.5 KB · Views: 2
Ok, that work! Deserialize the name its a good workaround, dont understand from where this bug come from, and if is related with other names issue like the Refresh potions and maybe to all other pots
 
Nice, now pots seems works fine. I've found another item, there are a version of "Crossbow bolt" named "Bolt", its not critical, u can use it anyway.
I will focus on this stuff this weekend. I will also go through TileData the best I can and try to fix any nuances I find along the way as well. Any others noted feel free to mention them.
 
Spectre updated Adventurers of Akalabeth with a new update entry:

Archmage - 25 February 2024

-Both client and server update.

-Research bags now hold 50,000 of need items
like scrolls, quills, and ink.

-Character level now calculates with the game
skill setttings for any set extra skill points.

-Client `music` folder renamed to `Music` to
reduce confusion for Linux clients.

-Strange portals now have an entrance and exit and their graphic changed.

View attachment 23199

-Ancient Spellbooks are now in the game.
- Part of the spell research...

Read the rest of this update entry...
 
This is cool!! The spell research system needed some polish, it was a bit awkward to use. Good job!

I have 2 possible bugs to report to you, the first is that you can't get off boats or magic carpets via the plank, the only ways I've found to get off the plank are with teleport magic or by walking in stealth on it, I think It's a z-level problem, because on some docks it goes down simply by walking, but not on coasts, instead walking in stealth do a kind of teleport.

The second, I don't know if it's a bug or something intentional, but the elemental forges that are scattered around the lands, which enchant weapons with some elemental buffs, don't work with ranged weapons, but as I already said it could be intentional.

I also have a suggestion. When creating the character and leveling it, I find myself having to always include Magery, for the one and only reason that I need a way to mark the runes.
I know there are alternative ways to travel and mark runes, but they require a certain knowledge of this "shard" and are not quite as handy as a magery book, bought from the vendor and a mark & recall scroll found in one of the normal dungeons in the lands of Sosaria.

There are maps found in dungeon chests, the trail maps, these maps work as "recalls" towards certain locations, and can be used with tracking and cartography with a limited number of uses, but they are quite rare, it would be interesting if could be crafted like a "mark" and used like a "recall".

Or you could implement a script like this.

https://www.servuo.com/archive/advanced-camping-outpost-system.1420/

but camping already has its own mini-system and use, but I found the travel part interesting.

Keep up the good work, and thank you for your efforts and for keeping Djeryv's work alive
 
Last edited:
I have 2 possible bugs to report to you, the first is that you can't get off boats or magic carpets via the plank, the only ways I've found to get off the plank are with teleport magic or by walking in stealth on it, I think It's a z-level problem, because on some docks it goes down simply by walking, but not on coasts, instead walking in stealth do a kind of teleport.
I had the same issue, check if you turned on auto-run. Game has lifeguard vibes about it, no running lol
 
the elemental forges that are scattered around the lands, which enchant weapons with some elemental buffs, don't work with ranged weapons, but as I already said it could be intentional.
Yup, you can't really use wooden weapons on forges. Probably because they would start on fire.
When creating the character and leveling it, I find myself having to always include Magery, for the one and only reason that I need a way to mark the runes.
I usually take magic resist and healing myself.
I know there are alternative ways to travel and mark runes, but they require a certain knowledge of this "shard".
True. Learning is FUNdamental.
 
One feature I'd like is to have is a scalable configuration option (10% to 100%) for the more painful non direct damage traps.

Instead of destroying all arrows, potion, etc, destroy a percentage of them. Instead of destroying gear (or at least artifacts), damage the gear a percentage calculated off of the max durability of that gear. It would still be a painful lesson but not quite as discouraging to someone who doesn't have a high trap removal skill/trap removal gear created from high skills.
 
Status
Not open for further replies.
Back