Make Ultima Online Great Again | zuluHOTEL Zulu Hotel Canada https://zuluhotel.ca Mon, 16 Sep 2019 04:32:41 +0000 Mon, 16 Sep 2019 04:32:41 +0000 PicoCMS Armor Rating and the Protection Spell <p>I think a lot of folk misunderstand the amount of work that has to go into taking a stock RunUO install and paring it down to the T2A or Renaissance era with any degree of fidelity. With AOS and its wacky new protection stats system, anyone developing a pre-AOS server really needs to pick through the source rather meticulously. RunUO is littered with conditionals for Core.AOS and in some cases the focus on the later eras has meant that the non-AOS cases have fallen by the wayside.</p> <p>Case in point: Protection.</p> <pre><code>public override void OnCast() { if ( Core.AOS ) { if ( CheckSequence() ) Toggle( Caster, Caster ); FinishSequence(); } else { if ( m_Registry.ContainsKey( Caster ) ) { Caster.SendLocalizedMessage( 1005559 ); // This spell is already in effect. } else if ( !Caster.CanBeginAction( typeof( DefensiveSpell ) ) ) { Caster.SendLocalizedMessage( 1005385 ); // The spell will not adhere to you at this time. } else if ( CheckSequence() ) { if ( Caster.BeginAction( typeof( DefensiveSpell ) ) ) { double value = (int)(Caster.Skills[SkillName.EvalInt].Value + Caster.Skills[SkillName.Meditation].Value + Caster.Skills[SkillName.Inscribe].Value); value /= 4; if ( value &lt; 0 ) { value = 0; } else if ( value &gt; 75 ) { value = 75.0; } Registry.Add( Caster, value ); new InternalTimer( Caster ).Start(); Caster.FixedParticles( 0x375A, 9, 20, 5016, EffectLayer.Waist ); Caster.PlaySound( 0x1ED ); } else { Caster.SendLocalizedMessage( 1005385 ); // The spell will not adhere to you at this time. } } FinishSequence(); } }</code></pre> <p>The above snippet is from the Protection Spell. If you look closely, you'll see that if Core.AOS is false (which it is for our server), then the <code>Toggle()</code> function never gets called. Instead, it does a few checks, adds the spell object to the registry, starts a timer.... and then does nothing. There is no armor bonus granted when using this spell. Arch Protection works; and if you poke at the code you'll see it has its own, different logic.</p> <p>Daleron and I also spent some time digging into just how exactly one's armour rating or AR is calculated. It's something I'd always sort of glossed over; one of those things that just works in the background and you never bother with it. Turns out it's pretty simple in RunUO, though some of it seems pretty arbitrary:</p> <ul> <li>Every layer/slot is multiplied by a scalar</li> <li>Those products are then summed together</li> <li>Damage reduction is calculated as follows: damage absorbed = 0.5<em>AR + 0.5</em>AR*[random number between 0 and 1]</li> </ul> <p>The AR summation is so that different pieces contribute a different amount. Gorgets and gloves contribute 7%, helmets contribute 14%, arms/pauldrons contribute 15%, legs 22%, chestplates 35%, and finally shields contribute 100% of their AR rating to your total.</p> <p>Conveniently, the Zulu Archive tells me that a suit of Ryous provides 100 AR. Based on the RunUO internals it should work out something like this:</p> <p><img src="/assets/ryous.jpg" alt="Ryous" /></p> <p>In POL, it's hilariously buggy. first of all, in the itemdesc.cfg the Ryous items are all set for an AR of 70, not 100. More on that later. Now, In my copy of ZHF, built with POL093, every item has an equipscript and an unequipscript that live under scripts/control/. After some digging I found the relevant functions.</p> <p>When equipping:</p> <pre><code>function DoArMods( who , item ) var ar := Cint(GetObjProperty( item, "ArBonus" )); if( ar ) who.ar_mod := who.ar_mod + Cint(ar/2); endif endfunction</code></pre> <p>When unequipping (irrelevant stuff removed):</p> <pre><code>program unequip( who, it ) [...] temp := Cint(GetObjProperty( it, "ArBonus" )); if( temp ) who.ar_mod := who.ar_mod - Cint(temp/2); endif [...] endprogram</code></pre> <p>Let's just take a sec and list the actual armor values I got from ingame testing, one piece at a time.</p> <ul> <li>Gloves: 4 AR</li> <li>Gorget: 4 AR</li> <li>Helmet: 9 AR</li> <li>Arms: 9 AR</li> <li>Legs: 9 AR</li> <li>Chest: 30 AR</li> <li>Shield: 32 AR (dependent on Parrying skill)</li> </ul> <p>Tallying these numbers gets you 97.</p> <p>The <strong>ArBonus</strong> and <strong>ar_mod</strong> CProps get set when you equip items with a bonus to armor, such as a helmet of hardening. Unfortunately POL093 does AR processing in the core which is closed-source, and documentation is hard to find these days. As best I can tell, though, it's all fucked up.</p> <p>What if you equip the gloves and then the legs, you should have 13 AR, right?</p> <p>Wrong. You have 14. Because POL.</p> <p>The Ryous shield on its own bestows a mighty 32 AR. Well, if I equipped the legs and the gloves for a total of 4 + 9 = 14, then maybe I will get even more than 36!.</p> <p>No. Fuck you. You get 33. The gloves only count for 1 if you have a shield on.</p> <p>Well what about all 3? It should be, er.... well it should be 45.</p> <p>Wrong again! It's 43.</p> <p>How about just the legs and the arms? 18? Nope, 19.</p> <p>It goes on and on, but basically the pieces give different ratings depending on when you equip them. The whole suit, irrespective of order, eventually reaches 99 AR with the shield. That's not 100 (info vault), it's not 70 (itemdesc.cfg), and it's not 97 (grade 3 math). The funniest part to me was that if you equip everything but the shield you get 69 AR, and then get ripped off for that last 2 AR when you put the shield on and only get 99, not 101.</p> <p>Somebody, somewhere, wrote some really funky code in the old POL core(s).</p> <p>And this is why all serious UO development has moved to RunUO.</p> <p>As always, get at me on IRC.</p> Mon, 08 Apr 2019 00:00:00 +0000 https://zuluhotel.ca/devblog/armor-protection https://zuluhotel.ca/devblog/armor-protection Battle Royale <p>Confession: I play a lot of PLAYERUNKNOWN's BattleGrounds (hereafter PUBG). I like the Battle Royale idea of a massive free-for-all with one victor left standing at the end.</p> <p>So when searching for an interesting way to test PVP mechanics in our upcoming Beta Test, we were kicking around a few ideas. A generic &quot;pvp arena&quot; works for a bit but after a while you get bored of just duelling. So Daleron and I decided we'd just pound out a bastardization of PUBG in UO.</p> <p>For those of you who haven't played PUBG or its more-successful clone, Fortnite, the premise is thus: 100 players parachute onto an island, armed with nothing, and fight to the death with whatever gear they can find. There are weapons, armor, grenades, healing supplies, and all that good stuff sitting on tables and floors scattered across the huge island of Erangel (and later other maps). This loot is waiting for the player in houses, hunting cabins, hospitals, power plants, and apartment buildings. You gear up as best you can and then it's kill or be killed.</p> <p>Meanwhile, a timer is constantly counting down: There are three &quot;circles&quot; you need to care about in PUBG: Red, white, and blue. The Red circle appears occasionally and anywhere inside its radius gets hit with a massive artillery strike. The Blue circle forms the boundary between safe and danger zones. Outside the Blue Circle you take damage over time and inside it you are safe (from the zone, not from other players). Periodically, the Blue Circle begins to contract, forcing players ever closer together. The White Circle does nothing except show where the Blue Circle will pause. Ignoring the silly Red zone, the game flow goes like this:</p> <ol> <li>Parachute from a C-130 Hercules onto the island</li> <li>White zone appears</li> <li>Blue zone slowly contracts until it reaches the white zone, then stops</li> <li>White zone contracts</li> <li>Wait a short period of time</li> <li>Go to step 3, repeat until one player remains.</li> </ol> <p><a href="/assets/uopubg2.png"><center><img src="/assets/uopubg2-thumb.png"></center></a></p> <center>(Click to enlarge)</center> <p>I decided to implement this as a <a href="https://en.wikipedia.org/wiki/Finite-state_machine">Finite-State Machine</a> with some adaptations to fit the PUBG game flow (which is of course a first-person 3D shooter) into UO (a tile-based third-person isometric pseudo-3D game): </p> <pre><code>public enum BattleState { Idle, //game not running, nobody able to join Joining, //players able to join Parachuting, //if it was pubg you'd be in the herc or parachuting Playing, // game in progress }</code></pre> <p>In Zulu Hotel our circles are actually rectangles because it's tile-based and the coordinate system is rectangular, and thus makes the math easier/faster. We use the graphics from the <a href="http://www.uoguide.com/Energy_Field">Energy Field</a> spell and others to denote the White and Blue zones. We don't have Red zones.</p> <p>Instead of Erangel, our game is played on Verity Isle (aka Moonglow Island). It turns out this is a great place for this minigame because it's massive and has several distinct areas already in: There's the city of Moonglow itself, with the docks and the bank, the Lycaeum, the Telescope, a grave yard, the Council of Mages stronghold, large expanses of wooded terrain, and farmhouses dotting the rest of the island. Instead of assault rifles and energy drinks, our island has katanas, war maces, and other weapons scattered about. You can find arrows for your bow, magic reagents, bandages, potions, and all manner of shields and armor.</p> <p>Instead of parachuting from a Hercules, when <code>BattleState</code> is in the <code>Joining</code> state, the game just kills all its participants and lets them disperse freely across the island as ghosts. When the countdown is complete we call the BeginPlay() function:</p> <pre><code>public static void BeginPlay() { foreach( Mobile pm in _Players ){ pm.Resurrect(); _AlivePlayers.Add(pm); pm.OnDeathEvent += OnPlayerDeath; pm.SendMessage("Last one standing in Moonglow wins!"); } _state = BattleState.Playing; _ZoneCenter = _FinalZoneCenters[ Utility.RandomMinMax(0, _FinalZoneCenters.Length - 1) ]; _CurrentStageEnumerator = _ZoneStages.GetEnumerator(); _NextStageEnumerator = _ZoneStages.GetEnumerator(); _NextStageEnumerator.MoveNext(); ZoneTick(); HandleZoneDamageTimer(); }</code></pre> <p>Right at the top of the function you can see that it iterates through all the Mobiles in the list of players (<code>_Players</code>), resurrects each player, and then play begins. From there the game follows the same flow as I outlined above, alternating between contracting the zone and waiting for someone to win, until the final zone is just a single tile and any remaining combatants are literally forced to stand on top of each other at which point I suspect Warrior-spec characters will probably have a distinct advantage.</p> <p>Speaking of classes, I'm excited to see how skilled players will play to their strengths and make the terrain work for them. I went through and hand-chose a long list of what PUBG players call &quot;final circles&quot;: points on the map about which the zone contracts to its smallest. I chose about 40 points with a good variety of characteristics: Some urban, which I imagine will benefit Warrior-spec characters since they can use obstacles for cover. Some wooded and open, which I imagine will benefit Mages and Rangers. Powerplayers gonna do what powerplayers gonna do.</p> <p>There's no reason you can't take a Bard or a Thief into the arena, of course, but fair warning: I've been focusing my efforts on &quot;the Big 3&quot; pvp classes, and we'll get to the others afterwards. Some of the later Zulu Hotel spin-offs and clones implemented a &quot;Song book&quot; for Bards to cast spells, but I'm taking an old-school approach: Get the fundamentals of the original ZH2 and/or ZH3 correct and then see where it goes from that point. We have Necromancer and Earth spellbooks for Mages but that's it so far. If a &quot;Song book&quot; or whatever ZH Scandinavia calls it needs to be added we can do that in the future.</p> Fri, 06 Sep 2019 00:00:00 +0000 https://zuluhotel.ca/devblog/battle-royale https://zuluhotel.ca/devblog/battle-royale Beta Keys <p>When Daleron and I decided that we wanted to hold a limited-access Beta Test for Zulu Hotel, we considered a number of different approaches, and in this post I will compare and contrast the three main potential courses of action. But first, a quick look at the requirements of any solution.</p> <p>Our priorities for Beta Testing are, in no particular order:</p> <ol> <li>Limit access to a reasonable number of testers so that it's easier to manage.</li> <li>Avoid unnecessary complexity which can lead to an increase in attack surface size or number of potential vulnerabilities, and adds a time/opportunity cost if it breaks.</li> <li>Get a diverse group of testers involved, to avoid groupthink and tunnel-vision.</li> <li>Minimize ongoing work requirements, i.e. we wanted a &quot;set it and forget it&quot;-type of solution.</li> </ol> <p>It turns out that there aren't a lot of solutions out there that tick all of those boxes, but here are a few we looked at.</p> <p>The first and most straightforward is to manually hand out access, in the form of account credentials. The <code>admin</code> command can create accounts with easy temporary passwords, and there's a <code>password</code> command for players to secure their accounts after the fact. However this solution doesn't really scale well. Quite frankly, each of us have better things to do than copy-pasting account credentials into IRC or an email (like, say, developing. Or our actual jobs).</p> <p>So that option went right out the window. The next thing we considered was, well, maybe we can create a web-based account request form where you register with a username/password combo and it requires a special code to complete the registration. I liked this idea but the more we discussed it the more it seemed like it would balloon out of control (criterion #2) and we'd still have to find a way to distribute beta invite keys on reddit or other venues (criterion 4).</p> <p>On top of that, UO shards have a history of—shall we say—brittle connections with web-based tools. Web-based account creation modules used to be an easy way to launch denial-of-service attacks on <a href="http://polserver.org">POL-based</a> shards, and frankly I'd rather have fewer web-facing gateways anyway.</p> <p>We toyed briefly with maybe hacking the client to take a third credential (i.e. username, password, <em>and</em> access code) but that would be an enormous amount of work, and so on a random weeknight back in August I decided. Fuck it. We'll leave auto-account on, and delete all the starting locations at character generation time, so that you can only start in <em>The Lobby</em>, which is just a tiny little island with nothing on it off the coast of Trinsic. That's it. There's nothing else and you can't do anything there. But this is <a href="https://reddit.com/r/prequelmemes">where the fun begins</a>: A player who's already in the Beta doesn't start in the Lobby - they start in Serpent's Hold, and from Serpent's Hold you can access the Beta's single dungeon (the Fire dungeon of course, but with different spawns). The little island attached to Serpent's Hold has some &quot;nature&quot;-type spawns as well for training Animal Taming, Herding, and the other related skills. But every single spawn has a small chance to drop a new item, called a Fiery Moonstone which, when double-clicked, will evaporate. When it does, you will hear the spirits whisper the words of power to you.</p> <center><img src="/assets/moonstone.png"></center> <p>The words of power are drawn at random from the Britannian phonetic alphabet:</p> <ul> <li>An</li> <li>Bal</li> <li>Corp</li> <li>Des</li> <li>Ex</li> <li>Flam</li> <li>Grav</li> <li>Hur</li> <li>In</li> <li>Jux</li> <li>Kal</li> <li>Lor</li> <li>Mani</li> <li>Nox</li> <li>Ort</li> <li>Por</li> <li>Quas</li> <li>Rel</li> <li>Sanct</li> <li>Tym</li> <li>Uus</li> <li>Vas</li> <li>Xen</li> <li>Wis</li> <li>Ylem</li> <li>Zu</li> </ul> <center><img src="/assets/invitecode.png"></center> <p>That string of words you receive from the evaporating stone is the invitation code. If you as a player in-game issue the <code>beta</code> command you will be presented with a gump asking for your invite code. A successful code will allow one character (and only one) to leave the Lobby and join the rest of the Beta Testers. (You can see here I made a typo).</p> <center><img src="/assets/betagump.png"></center> <p>On the technical side, what's happening is that the invite stone item creates a random string from the above list and stores it into a key-value hash table (a Dictionary in C# parlance) where the string is the key, and a Boolean is the value, defaulting to <code>false</code>. When someone correctly enters an existing key it gets marked as <code>true</code> in the hash table, meaning it has been used. Could it be brute-forced or guessed? Absolutely. But even using just four words in a beta key means we have about 0.0002% chance of guessing correctly, and to brute-force all 457000 permutations in hopes of finding a key that's been generated but not used would take about 4 years. And frankly, if someone will go through all that just to get into the beta test, then that's the kind of tester I want!</p> <p>The more we thought about this solution the more it makes sense: It's low-maintenance, it encourages play (friend wants an invite code? Go literally hunt for one!) as opposed to AFKing, it doesn't expose any additional attack surface, and when we're done Beta I'll just simply set <code>Core.Beta = false;</code> and the items will stop dropping in loot. Minimal, mostly in-game driven, etc.</p> <p>Stay tuned - next I'll be writing about our new combat minigame.</p> Thu, 05 Sep 2019 00:00:00 +0000 https://zuluhotel.ca/devblog/beta-keys https://zuluhotel.ca/devblog/beta-keys Humility (no, not the shrine) <p>Sometimes your intuition is just totally wrong.</p> <p>A while back I wrote <a href="/devblog/skillgain-part-2">a fun little post</a> about different functions for computing the chance to gain skill. You might recall that your humble devblogger suggested that the natural logarithm would be slow and unwieldy when trying to approximate the complementary error function, and that a polynomial solution was better.</p> <p>Well, I actually did some benchmarking today on my thinkpad (Sandy Bridge i7, 8GB). Turns out that C# doesn't even have an implementation for erfc() so I just tested the other three:</p> <pre><code>jester@thinkpad:~/documents/code/csharp$ mono benchmark.exe Iterating to 1 Testing Cosine method... Done. Iterations: 1, Elapsed: 9.051ms Testing Polynomial method... Done. Iterations: 1, Elapsed: 0.037ms Testing Logarithm method... Done. Iterations: 1, Elapsed: 0.012ms Iterating to 10 Testing Cosine method... Done. Iterations: 10, Elapsed: 0.015ms Testing Polynomial method... Done. Iterations: 10, Elapsed: 0.027ms Testing Logarithm method... Done. Iterations: 10, Elapsed: 0.034ms Iterating to 100 Testing Cosine method... Done. Iterations: 100, Elapsed: 0.024ms Testing Polynomial method... Done. Iterations: 100, Elapsed: 0.094ms Testing Logarithm method... Done. Iterations: 100, Elapsed: 0.025ms Iterating to 1000 Testing Cosine method... Done. Iterations: 1000, Elapsed: 0.138ms Testing Polynomial method... Done. Iterations: 1000, Elapsed: 0.681ms Testing Logarithm method... Done. Iterations: 1000, Elapsed: 0.13ms Iterating to 10000 Testing Cosine method... Done. Iterations: 10000, Elapsed: 1.284ms Testing Polynomial method... Done. Iterations: 10000, Elapsed: 5.977ms Testing Logarithm method... Done. Iterations: 10000, Elapsed: 1.022ms Iterating to 100000 Testing Cosine method... Done. Iterations: 100000, Elapsed: 11.411ms Testing Polynomial method... Done. Iterations: 100000, Elapsed: 46.885ms Testing Logarithm method... Done. Iterations: 100000, Elapsed: 6.943ms Iterating to 1000000 Testing Cosine method... Done. Iterations: 1000000, Elapsed: 69.204ms Testing Polynomial method... Done. Iterations: 1000000, Elapsed: 225.343ms Testing Logarithm method... Done. Iterations: 1000000, Elapsed: 34.558ms Iterating to 10000000 Testing Cosine method... Done. Iterations: 10000000, Elapsed: 420.749ms Testing Polynomial method... Done. Iterations: 10000000, Elapsed: 2069.943ms Testing Logarithm method... Done. Iterations: 10000000, Elapsed: 346.445ms Iterating to 100000000 Testing Cosine method... Done. Iterations: 100000000, Elapsed: 4179.357ms Testing Polynomial method... Done. Iterations: 100000000, Elapsed: 20475.724ms Testing Logarithm method... Done. Iterations: 100000000, Elapsed: 3452.435ms</code></pre> <p>Ignore that first aberrant result - several orders of magnitude slower than the next trial. That's not a typo, just a small bug in the benchmarking code that I wrote. The extra delay is due to the constructor for DateTime being called (and probably nothing being cached yet). In reality, that first calculation takes only 12 microseconds or so.</p> <p>You can see that the Polynomial method is significantly slower, taking upwards of 20 seconds to crunch that equation 100 million times. I was surprised to see the logarithm method run almost 7 times faster! The default RunUO method is just a little bit slower than the Cosine method here.</p> <p>Just goes to show that it's better not to assume, especially when it comes to software performance. This covers the probability to gain for a single attempt: later on in a different post I'll examine how that translates to what the player experiences.</p> Fri, 10 Aug 2018 00:00:00 +0000 https://zuluhotel.ca/devblog/humility https://zuluhotel.ca/devblog/humility Q and A <p>Believe it or not I'm sitting on a seaside patio in Hawaii doing Zulu Hotel stuff. It's been a rainy day and my wife is at the pool with our 2-year-old daughter so I figured, why not?</p> <p>Work is still ongoing, and the good news is that we've figured out the last few hurdles that were in our way to making the Magic system work the way we wanted. The bad news is that fruity drinks, sun, and sand are getting in the way of my productivity. In any case, a few questions were asked on IRC and I didn't answer because I've not been very diligent at checking IRC lately. So without further ado:</p> <p>Q: Kieeps asks if we will strip anything &quot;non-zuluish&quot; out of the server such as Champion Spawns. A: I wrote in IRC that the short answer is &quot;yes&quot; but the more I think about it the more I think that's a bad answer. We're going to have an invite-only playable beta very soon, probably within the next month. It's highly unlikely that that playable beta will feature Champion Spawns or other capstone content, but that doesn't mean we're stripping it from the game. The code is still there, but the spawns are disabled. Specifically regarding Champion Spawns we may introduce them in the future (or not, depending on what players want to see) but other OSI-specific systems will likely be out. You won't see Imbuing in the final product, for example. Q: A user named &quot;bol&quot; asked if Zulu Hotel is open-source or free-software and if they could try a beta/see the documentation. A: The documentation isn't really available or written yet, but the answer to the first question is yes. We're building this server on top of RunUO and ServUO which are both licensed under the terms of the GNU General Public License Version 2, which you can read about on gnu.org</p> Mon, 01 Apr 2019 00:00:00 +0000 https://zuluhotel.ca/devblog/q-and-a https://zuluhotel.ca/devblog/q-and-a Resource density maps for fun and profit <p>Let's face it. UO's crafting is simplistic and not super fun. You double click your hammer, double click the ore, click some menus, clank clank clank, and you've got a new Peachblue platemail gorget. When you mine for ore, or chop wood for lumber, the game pretty much just checks your gathering skill against a list of &quot;thresholds&quot;, one for each resource. As your skill grows, so does the list of ores that you can gather. OSI's implementation involved hitting &quot;veins&quot; of various ores. If you hit an iron vein, that's all you'd get until the vein despawned or was exhausted.</p> <p>The implementation in POL (and thus original Zulu Hotel) was at once more simplistic and more complicated. In my old POL093 copy I found lying around which appears to be based on ZH Scandinavia, it goes something like this:</p> <ol> <li>Call a core function <code>HarvestResource()</code> to see if you strike any resources, and the base amount that you receive.</li> <li>Roll a 1d100 (a 100-sided die)</li> <li>Compare that dice-roll against a list. If you roll something like 93 or above, you get the special gems like Radiant Nimbus Diamond. If you roll 4 or less your tool breaks.</li> <li>Roll another 1d100</li> <li>Calculate your harvesting skill divided by 5, plus 35.</li> <li>Check the number from step 5 against the roll from step 4. If your step 4 roll is less than the result of step 5, you get coloured ore. Otherwise, you get iron.</li> <li>Assuming you are getting coloured ore, then you calculate the maximum amount of gatherable ore by dividing your mining skill by 30, and rounding down. Double that if using an Omero's.</li> <li>Then you roll 1d155 and compare that number to the difficulty ratings in the ore definition file.</li> <li>Assuming the player can actually mine it, then you multiply that value from step 1 by a bunch of things including the tool bonus from Omero's/Xarafax and also from being a spec crafter.</li> <li>Oh, also you get double ores if you are spec.</li> </ol> <p>Yuck.</p> <p>Why not strive for a solution that's at both more elegant and more extensible? The system we're building will do the following:</p> <ol> <li>(When writing the server) Give each ore/log/whatever an Abundance rating that describes how rare it is. Iron has a very high abundance. Nimbus has a very low abundance.</li> <li>Determine which ores are actually present in the given area that the player is trying to mine. Make a list of these.</li> <li>Take the list from step 2 and remove all the ores that the player is not skilled enough to harvest.</li> <li>Take the list from step 3 and normalize the abundance ratings so that they add up to 100%.</li> </ol> <p>Now it's a simple matter. If you're standing in Minoc Mines, and the normalized abundance rating of Dripstone comes out to 36%, then you will receive Dripstone ore approximately 36% of the time at that location.</p> <h4>Why is this better?</h4> <p>If it ain't broke, don't fix it, right? Well, okay, the system from POL isn't <em>broken</em> per se, but it's not easily maintained or altered. A simpler system lends itself well to expansion. Here's a little thought experiment. </p> <ul> <li>Imagine you have a stack of plain white paper, like you would use in a printer.</li> <li>Take one piece of paper at a time and make a single dot on each page with a pen or pencil. Make the dot in a random place on each piece.</li> <li>Now imagine you are a really great artist (I'm not, but maybe you are) and you gradually shade the piece of paper with your pencil, getting lighter and lighter as you move away from the dot you just made. In every direction you uniformly get lighter at the same rate.</li> <li>At some places you'll hit the edge before you get to fully white. At some other places you'll hit fully white well before the edge.</li> <li>Now label all the pieces of paper with a different ore, starting with iron and moving on up to Nimbus. Now put them all back into a stack.</li> </ul> <p>What you have now is a series of layers, not unlike a photoshop document. Each layer describes the abundance of the resource. So an inch away from the dot you made initially, you might be at 60% darkness. That particular piece of paper is labeled &quot;pyrite&quot;. So Pyrite ore is 60% abundant at that particular spot. Make sense?</p> <ul> <li>Now imagine that your piece of paper is the shape of Britannia.</li> <li>Now imagine that the server moves the virtual dots around on the virtual pieces of paper, so that concentrations of ore will actually drift (very slowly) over time. </li> </ul> <p>At server birth you might be able to strike Goddess ore in Minoc mines, but a few months later Goddess might be very rare. It might be possible to find a location where only Nimbus or New Zulu is abundant in any significant numbers. That means as long as you are skilled enough you would get Nimbus or New Zulu every single time.</p> <h4>What are the gameplay implications?</h4> <p>In my mind this system has a lot of potential upsides, some of which are:</p> <ul> <li>It creates an opening where people can play the market. Maybe Lavarock (which grants protection against fire damage) is super rare right now, but you've been stockpiling it for weeks. Time to sell it to people grinding fire elemental lords at a hefty premium.</li> <li>It creates more incentive to go out and &quot;hunt&quot; for good resource spots instead of everyone just macroing AFK in Minoc mines. * It might be far more profitable to mine on one of the random mountainsides near Skara Brae, or inside Despise. Then again, it might not. Players have to go discover by themselves.</li> <li>It also creates a market for those same spots. I'll sell you a rune to a great spot for mining Ebon Twilight Sapphires for fifty thousand gold pieces.</li> <li>By the same token, PKs can go out hunting miners, and the players scouting mining locations. This creates additional chances for random &quot;field pvp&quot; to occur and makes the game exciting.</li> </ul> <p>Coupled with some other changes we've got planned for crafting, I think this will really open up that side of the game.</p> Tue, 05 Apr 2016 00:00:00 +0000 https://zuluhotel.ca/devblog/resource-density-maps https://zuluhotel.ca/devblog/resource-density-maps Macroing and Skill-gain <p>The simple fact is that macroing sucks.</p> <p>It's been said time and time again: The usual progression on a shard goes something like <code>macro -&gt; pvp -&gt; tower -&gt; bored -&gt; quit</code> and it never seems to change. That's why shard launches are usually swarmed with players before the inevitable drop-off in population. People love that rush to get the first GM, the best housing spots, the first complete template, the early loot that gives you a huge advantage because the other nubcake only has 75 parrying.</p> <p>That's not really a sustainable model, though. A shard that wants to be successful in the long run needs to make it possible for new players to &quot;catch up&quot; to the veterans without it being too much of a chore, and I'm not talking about just starting with X number of skillpoints. All that does is make people macro a little less, and be a little less attached to their characters.</p> <p>The real problem is that <em>modern UO imposes a waiting period before you can actually play the damn game.</em></p> <p>Think about it: You sign up for a shard. Unless you've got a buddy who's already established, you have to find some random place in town to hide while you macro your skills up to the point where you can at the very least not die in a dungeon and maybe possibly be able to escape PKs by Recalling or hiding or just surviving until you make it back to a guard zone. How long does that take? Days. Weeks sometimes. Meanwhile you've got fully-geared and -templated PKs hunting you every time you set foot in Despise to find your first Vanquishing weapon, and you've got no house to macro safely in. That sucks. The design of the game literally encourages you postpone playing until after macroing.</p> <p>Generally, there are two approaches one can take:</p> <ol> <li>Discourage macroing with punitive mechanics/rules</li> <li>Encourage players to actually play in lieu of macroing</li> <li>Reset/wipe the shard on a regular basis.</li> </ol> <p>The first usually manifests itself as &quot;anti-macro&quot; rules or caps on the number of skill points a given character can earn per day. The second usually takes the form of things like &quot;power hours&quot; where skill gain is greatly boosted in the hopes that people will play when it's not their Power Hour, and the third simply discourages people from sticking around.</p> <p>I think the ideal approach lies in a hybrid approach, and assuming:</p> <ol> <li>Players will always macro if given even the slightest opportunity, and</li> <li>Players will typically take the path of least resistance.</li> </ol> <p>I think that given the preceding two points, the best approach becomes obvious: Make macroing less rewarding than actually playing one's character. Bear with me, here.</p> <ul> <li>Make skillgain exponentially slow</li> <li>Make skillgain significantly faster in select areas, such as dungeons and designated &quot;crafting areas&quot; for characters without combat skills</li> </ul> <p>Combining the bulleted points above results in a system that punishes (but tolerates) those who sit in their houses and macro, while rewarding those who go out and macro in a dungeon. I envision tweaking the gain rates so that a player who spends, say, 3-5 hours a day in a dungeon could have their character fully skilled in 1-2 weeks (whereas 24/7 macroing might not get you there for a month or more). Any shorter, and you run into characters being so easy to build that they become throwaways and people don't develop attachment. Any longer and the player starts to lose sight of the goal. If the player feels they are making significant progress, they are engaged, they have fun, they feel rewarded, and they stick around.</p> <p>Obviously this means you might have people macroing AFK in dungeons, but to be honest I think that that is a benefit, not a drawback. It might just be your lucky day if you found some goon sitting AFK in Hythloth with 500 of each reg on him. Again, it rewards the people who go out and actually play, while punishing the people who just want to AFK their way to GM.</p> <p>What about the player who wants to macro? As perverse as it might seem, I think there are a small minority of players who get satisfaction from setting up slick macros and automating everything. The real problem with macroing is that it encourages people to stay away from the game for weeks at a time and contributes to an inflated economy (more on the economy in a later post). Macroing is prevalent because it lets you get ahead of non-macroers, and so even those who don't really like to macro are forced to do so in order to keep up.</p> <p>Under this proposed system, the outcomes are reversed: It is far more productive to go hunt orcs for 3 hours than it is to macro for 3 hours. By the same token, nothing stops players from using macros to handle tedious tasks like filling potion bottles from a keg or scribing scrolls to sell. If implemented correctly, I think this system can be a real game-changer.</p> Tue, 03 Oct 2017 00:00:00 +0000 https://zuluhotel.ca/devblog/skillgain-part-1 https://zuluhotel.ca/devblog/skillgain-part-1 Skillgain Part 2: Electric Boogaloo <p><em>edit: see further discussion <a href="/devblog/humility/">here</a></em></p> <p>In my previous post I talked a little bit about skills and skillgain. Today I'm going to take a more in-depth look.</p> <p>Games typically start out rewarding quite liberally at the outset, and then when you're nice and hooked they start to wean you off those rewards bit by bit. A perfect example would be any game based on the d20 system that Dungeons &amp; Dragons uses: Your first level requires 300 xp, the second requires 900 xp, the 3rd level requires 2700, and so on and so forth. At the beginning, you get lots of shiny new feats and skill points to spend quite frequently, and then the pace at which you advance slows down progressively over the course of the game. The theory is that by the time you're nearing the 19th level and needing more than 300 000 experience points to level up, your character is not only more capable and can tackle tougher enemies and quests worth more experience, but you the player are more invested in your character. It feeds into the reward-seeking psychology that most of us have. Just one more level! You can do it!</p> <p>We have the same in UO, too. In OSI's system the sum total of your skill points is weighted against your pace of advancement to moderately slow you down. Exponential skillgain looks something like this:</p> <p><img src="/assets/exp.png" alt="Exponential Skillgain" /></p> <p>That's the graph produced using gnuplot of <code>y = -log( x/1400 ) * 0.2</code> where <em>y</em> represents the character's chance to gain a skillpoint as a percentage, <em>x</em> represents that character's current skill in tenths of a point (because this is how RunUO represents skills internally), and where <code>log()</code> is the natural logarithm. The factors <strong>1400</strong> and <strong>0.2</strong> are just empirically-determined factors to make the curve look the way I want. You can see from the curve that at low skill levels, your chance to gain is very very high, and that it decreases exponentially from there. For example, at 60 skill (600 on the chart), your chance to gain a skillpoint is about 20%.</p> <p>It's pretty straightforward, and it allows us to be flexible. Say we want the system to be able to scale a player's gain rate based on in-game factors automatically. We might want faster skill gains in a dungeon (as I've talked about previously), and we might want slower skill gains elsewhere for whatever reason. Well, it's easy to just apply a linear shift, like so:</p> <p><img src="/assets/linear.png" alt="Linear curve shifts" /></p> <p>Three curves, all the same function except the red one has been shifted upwards by 0.5 and the green one has been shifted downwards by having 0.5 subtracted from it. With a little imagination we can see how effective this is for situations where you want to automatically retard or accelerate skillgain rates. Power Hour? Add a few percent. Slow-gain zone of some kind? Subtract a few percent. This ties in really well with the ideas presented in a previous post about having very fast gains in dungeons and slow gains in houses to discourage everyone from just afk botting.</p> <p>Some of you might have already thought of the fact that this code needs to run every time anyone on the server swings a weapon, shoots a bow, steals something, hides, casts a spell, or crafts an item. In other words, it needs to be fast. Lightning-fast. Now, the natural logarithm isn't the speediest of calculations, but it's likely architecture-dependent. I'm not an expert in low-level CPU architecture, and I haven't done any benchmarking to see if <code>log(x)</code> is faster or slower than other functions, but I do know that modern CPUs are designed to do multiplication and other basic arithmetic very very fast. So let's see if we can find a function that's shaped like <code>log(x)</code> but is a little cheaper computationally-speaking. What might we try?</p> <ul> <li>The cosine curve from 0 to pi looks a lot like it.</li> <li>If you truncate the negative part of the Complement to the Error Function erfc(), it looks a bit like log(x)</li> </ul> <p>I can't imagine C# has a native erfc() function though, so we can try a polynomial approximation to it also.</p> <p>Those curves are overlaid here:</p> <p><img src="/assets/approx.png" alt="Approximations" /></p> <p>The blue curve is <code>erfc()</code>, the Complementary Error Function. The red curve is the Cosine function, the purple curve is the natural logarithm, and the green curve is the polynomial approximation to <code>erfc()</code>. None of them are exactly equal to <code>log(x)</code> but all are pretty close. The closest two are obviously the blue and green curves, which again are <code>erfc()</code> and the polynomial that approximates it is probably the best choice. After all, it's not necessary that the curve actually be exponential, only that it displays roughly exponentially-decreasing behaviour. I furthermore like that the polynomial curve is higher and isn't quite as steep in the low-skill areas which will make things more enticing for newbies as they'll see dramatic gains early on.</p> <p>Finally, we come to the idea that not every action is the same as another. Using Anatomy or EvalInt on yourself is not the same as using it on someone else. Maybe we want using Eval on yourself to be quite easy, since you already know roughly how smart you are. Maybe we want mages to be very hard to Eval since Mind Blast means they have a very real desire to keep their Intelligence scores to themselves. Well to do that we can also introduce a variable that represents the chance to succeed, which will act as a mitigating factor to the skillgain calculation.</p> <p>It looks something like this:</p> <p><img src="/assets/skillgain.png" alt="Varying chances to succeed a task" /></p> <p>Each coloured curve represents a different difficulty. The lowest curve represents a 90% chance to succeed, which is to say the task is very easy. An example might be a grandmaster shieldfighter parrying blows from a newbie macefighter. Just as in real life, the veteran should not expect to gain much valuable practice from fighting a newbie. As the newbie's skill grows, the veteran's chance to parry goes down. We might be on the 50% curve now, and you can see that the veteran has an increased chance to gain skill as he is fighting a more difficult opponent.</p> <p>This adds a bit of strategy to things as you will obviously see the most rapid gains from more difficult tasks, but at the same time you will succeed less if your task is difficult. Keeps us on our toes. It's incumbent on the player to find the optimal balance.</p> <p>More to come, thanks for reading!</p> Sun, 29 Oct 2017 00:00:00 +0000 https://zuluhotel.ca/devblog/skillgain-part-2 https://zuluhotel.ca/devblog/skillgain-part-2