Itsy's Janky Code Blog

... sit down, kick back and relax, and talk about anything that doesn't belong on one of the other forums.
Itesh
Posts: 828
Joined: Sat Feb 14, 2015 4:59 am

Itsy's Janky Code Blog

Post by Itesh » Tue Jan 11, 2022 6:09 pm

Following on from Elysia’s blog about building a master quest, I thought I might try this for coding- and so here we are, My Janky Code Blog. Why, I hear you ask? Well, mostly for giggles, and also because Ely said she’d read it and she is absolutely the glue that holds the game together, so it’s worth keeping her on-side.

At last count, the ideas list (it’s a thing) has ninety-two vague notes on it, comprising actual problems, desired features, huge future projects, bugfixes and stuff to make immortal lives easier (you know, a command to do that one thing). The topic that I’ve picked out is pet renaming!

Reasons to pick renaming:
  1. It’s got nothing to do with lag and I am fuelled by your anguish.
  2. We’re currently running janky mobol to allow pet renaming (or at least, maybe just horse renaming?) and as a point of principle janky mobol should be replaced by janky code.
  3. It’s the evening for me and I have limited time so this should be pretty easy, given I can copy/paste a lot of it
  4. Ely wants it done, and see above point about glue.
  5. I ate carbs today.
  6. I don’t feel especially well and so non-complex projects are the order of the day.
  7. The children are also ill and therefore there will be multiple disruptions – therefore non-complex yadda yadda.
Broadly speaking, there are a few ways we could implement this:
  1. A new command that does this one thing and nothing else.
  2. A broader command with subcommands that handles this and a future suite of associated commands – eg pet rename blah, pet info, pet diagnose etc
  3. Add a subcommand to an existing command
We’re going to go with option one, for the time being. Option two would be more suitable if we actually had a whole pet system involving training, leading pets around with a variety of skills etc and while that sounds wonderful for the future I am clearly not going to crap it out in the space of an evening. I cannot think of a relevant command off the top of my head we could use for option three – and this kind of thing mainly happens in Imm commands anyway. My horrible memory may be failing me here, though.

Here’s what I’m going with: name <target> <name>, eg, name dog Binky.

Here are our conditions for allowing the name to go ahead:
  • Victim must be following you, and you must be their master. Which might, to be fair, be the same thing, and I will crib from the order command for this. Big fan of using stuff that, crucially, already works.
  • Victim must be an NPC
  • Victim must also NOT be a humanoid. I could limit this to animals and horses and both kinds of birds but I think !HUMANOID is probably as much as we want to do here. Not there are a huge amount of pet snakes around.
  • While I think of it, we must specifically exclude rats and ravens from this, with the same error message as the ‘target not found’ so players, who are both cheeky and sneaky bastards, cannot use this to locate shadoweyes easily.
  • No-one involved is fighting.
  • The player’s position must be <= RESTING. I think it’s reasonable to rename your dog while sitting down.
I go to the Spawn and insist it goes to sleep.

If I think of other sensible conditions en route, I’m sure I’ll mention them. Fingers crossed. And with that utterly minimal level of planning completed, onwards we go!

First, I enter ‘name’ into the command table, adding the usual options, which are something along the line of minimum level, minimum position, what actual function it calls, and a couple of other bits and bobs. It’s added at the bottom of the table as the penultimate. The last line now needs renumbering, and the constant for number commands incrementing.

If you’re interested, WoTMUD has 433 distinct commands – depending on which version of the MUD you’re using.

I go to the Spawn and insist it goes to sleep. It insists on back scratches. Spawn is bloody weird.

It’s sort of a lie that we have 433 distinct commands – one of the Imm commands has at 25 subcommands. I digress.

In the same file, we add a function prototype for our new command, right underneath the massive list of the same. A function prototype is basically a way to tell our compiler that a function exists and it should go look for it. This stops the compiler crapping itself if our function does not appear ahead of the main function, which is probably won’t. I wrote an example of that for you, and it was a painful waste of time.

With that, we’ve achieved a state where we could actually type ‘name’ into the game and it would go looking for the relevant function. What happens if the function doesn’t exist? I have no idea, and I don’t especially relish the idea of finding out. Given we’ve prototyped it, it might be a compiler error – alternatively, it might be fine until we actually called it. I feel like this is the kind of thing Aureus might know, but to be fair he went to school before he started his lucrative career as a romance fraudster.

Time to write our actual function! We’re going to place it in the general ‘stuff that mobs do’ file, which seems appropriate (later I changed my mind).

CircleMUDs, of which we are one, have a setup where commands use a kind of template (sort of). I write ACMD, and then a name in brackets, and that automatically gives me access to a number of arguments sent over automatically from the command table – this is a #define, which is a little like a mud alias.

I get to write:
ACMD(do_name)
Instead of:
void do_name(pointer to character data, string containing argument, int containing command ID, int subcommand ID)
…which I think we can all agree is, if not completely user-friendly, then at least not as awful as it could be. I presume the abbreviation is ACMD because it’s A COMMAND, but who knows.

When we use ACMD like this, we always get:
  • A pointer to the character (PC, mob, whatever) that entered the command
  • A string containing the argument, which in this case is everything they typed after the command itself. If I enter ‘kill Vampa he’s a goddamn idiot’, then my argument string contains ‘vampa he’s a goddamn idiot’. The mud trims leading spaces and casts to lowercase for ease of processing.
  • Command ID – which is just the number it appears in the command table.
  • Subcommand ID – not used that much, but basically where the same function is called by multiple commands – eg, narrate, chat etc – this lets the function know what kind of thing is using it, which is occasionally helpful.
This blog post is already far longer than the code will be. Explaining is taking forever.

What we don’t get, and what we definitely need, is a pointer to our victim, and we’ll be needing to extract that from the argument string.
I have no idea how to do that off the top of my head, but of course this happens all the time in the game, so I’m going to head over to ‘charge’ and steal the code from there - but first we need to work on some sanity checks – which is to say, reasons why we can’t do this.

Flash likes functions to exit as fast as possible, so our first round of checks is on the person who used the command – this way we don’t do unnecessary string processing.

We now exit if:
  • Player is an NPC
  • Player is fighting
  • Player’s position is below resting
  • Player has no followers (and therefore cannot be the master of something in the same room)
And now we check if Ditesh wishes to watch a film – and she does not.

We use a simple “send this string to character” function to send a fail message to the NPC – Imms occasionally switch into mobs to debug mobol, and it’s useful to know it’s a mob thing. Derp moment – we don’t need to check for position below resting because we’ve done that in the command table. We only need to check for fighting.

With that done, and the pre-victim sanity checks in place, we can turn to some argument processing – remember, we have a pointer to the person doing the command but we have nothing pointing to the pet we want to do it to.

We need to take account of players writing stupid stuff – there’s every chance our argument line could be ‘dog Binky the massive arsehole’, and we only want to assign Binky. It actually wouldn’t break anything to have the rest, but we probably don’t want people to do it anyway.
Luckily, I don’t have to actually do the string handling myself – there’s a built in function to strip out the first two commands from a string, so we declare two character buffers, arg1 and arg2, make them as big as they could be (mild waste of memory maybe but they’re destroyed when the function ends), and pass the argument variable, arg1 and arg2 into the function. Following this, the values of those buffers are:

argument: “dog Binky the massive arsehole”
arg1: “dog”
arg2: “Binky” (or possibly binky, I did say earlier this got lowercased)

Where’s the rest? Who cares. Not my problem, don’t have to deal with it.

Next, we steal the code from charge that takes the string ‘dog’ and finds the first visible thing in the room that corresponds to it, assuming dot notation (2.dog etc). If successful, a pointer is returned, and we’re calling that pointer ‘pet’, to make this all nice and clear.

Now we have:
  • A pointer to our main protagonist
  • A pointer to our victim, ‘pet’
  • A character buffer, arg2, which contains the word ‘Binky’
At which point we do more sanity checks, this time on ‘pet’, to whit:
  • If pet is a humanoid, return snarky message about asking them their name
  • Do they have a master, and is that master our player?
  • Are they fighting?
With that done, we steal the naming code from the petshop keepers, slap it in, change the variable and pointer names to match and we’re basically done. A quick read of the code for stray semi-colons while the testmud boots…

While we think of it, add NPC check to pet to stop people attempting to rename themselves. Come on, people.

Time for a test compile. I’m sure it’ll be one hundred percent fine and have no compilation errors at all. Absolutely.

Compiler Errors:
  1. Left in an old character buffer name that needed switching to arg2. Actually this happened like five times, turns out writing a blog is super distracting.
  2. The name finder that stops you naming your pet after Vampa was not declared in scope. So I declared it in scope.
  3. Deleted some asterixes thus converting pointers into arrays. Don’t ask because I will be unable to provide a coherent answer.
  4. TOO MANY ARGUMENTS IN NEW COMMAND AAAAAAAAAAAA okay so I deleted one comma before a right parenthesis and that fixed that.
And with that our errors are resolved and we proceed to testing.

Code: Select all

Rufferto, faithful dog of the sage starts following you.

* HP:Healthy SP:Bursting MV:Fresh > name dog Binky the massive arsehole
* HP:Healthy SP:Bursting MV:Fresh > l dog

A small sign on a chain around its neck says 'My name is binky'.

Rufferto, faithful dog of the sage is in excellent condition.
Qualified success! Lowercase b is not great, but my brain has almost entirely shut down, so I was going to leave that until I remembered I had to deal with this issue for one of the Gray Man features, and so once again we go on the scrounge for code we barely remember, and slip in this slinky line:

Code: Select all

if (islower(*arg2)) *arg2 = toupper(*arg2);
Hate coding yet? Me too.

Code: Select all

Rufferto, faithful dog of the sage starts following you.

* HP:Healthy SP:Bursting MV:Fresh > name dog Binky the massive arsehole
* HP:Healthy SP:Bursting MV:Fresh > l dog

A small sign on a chain around its neck says 'My name is Binky'.

Rufferto, faithful dog of the sage is in excellent condition.
We’re missing a confirmation message, which I am now too tired to contemplate, and further tests to make sure those sanity checks are working, but I’m pretty happy to leave this here for now. That can wait until tomorrow, when Aureus will tell me all about the horrific memory leaks this has introduced.

Oh god I’m tired.

Fin.

(Time Elapsed: About three hours, all told.)

Alvana
Posts: 175
Joined: Tue Dec 17, 2019 8:06 pm
Location: MO (USA)

Re: Itsy's Janky Code Blog

Post by Alvana » Tue Jan 11, 2022 6:52 pm

Too early to nominate this for log of the month?

Siro
Posts: 501
Joined: Tue Jan 29, 2019 3:38 pm

Re: Itsy's Janky Code Blog

Post by Siro » Tue Jan 11, 2022 8:29 pm

Alvana wrote:
Tue Jan 11, 2022 6:52 pm
Too early to nominate this for log of the month?
Yes. He gets more Qps when it's part of a series. So let's wait and see what happens...

Itesh
Posts: 828
Joined: Sat Feb 14, 2015 4:59 am

Re: Itsy's Janky Code Blog

Post by Itesh » Sun Jan 16, 2022 10:38 am

Today I'm going to write the blog first and then do the actual coding and then update with hilarious shenaniockups. Last time took forever and my spawn-free time is limited!

Here's the plan:

Test the petnaming thing aha no, we're not doing that.

Warrant and pardon commands for Justice clan members to use that can apply these things on the move without the need for mobol and with appropriate record keeping etc. Replacing mobol is *always* worthwhile.

General Format

warrant <playername> <insert sensible reason>

pardon <playername> <equally sensible reason>

In both cases, I'll be writing the reason and the people involved to player notes, along with their game locations when they did this. I doubt that'll ever be particularly useful, but if someone ever complains about so-and-so sitting in TVCS spamming warrants, it'll at least be tagged every time they did.

Once again, I'll be using the ACMD structure referred to in the first post, and this time as well a cheeky little stock circle function called half_chop (no, really), that takes and argument and returns two strings - the first word, and the rest. Here's an example:

Command Entered: warrant Vampa for off the cuff game design choices
Command: warrant
argument: vampa for off the cuff game design choices

half_chop(argument, arg1, arg1)
arg1: vampa
arg2: for off the cuff game design choices

I'm not going to take about sanity checks a huge amount, except to say that for the sake of clarity I'd probably write a function along the lines of...

Code: Select all

bool is_justice_clan(pointer to character data)
{
    if (is defender of the stone(character) ||
    is illian companion(character) ||
    ...
    ...) {
      return TRUE;
   }
   else {
      return FALSE;
   }
}
...just to make things a little less busy - and because it would be useful for future stuff.

Then we simply convert arg1 from a string to a character pointer using more or less the same way we did last time - i.e, steal code from elsewhere.

Next, generate a string for the note file like, I dunno:

Code: Select all

sprintf(buffer,"WARRANT: %s (%i) warranted by %s (%i) for %s.\r\n", name(arg1),room(arg1),name(protagonist),room(protagonist), arg2)
To offer some clarity here, sprintf writes a formatted string to character buffer (imaginatively called 'buffer' here, although that's not what it's called in the game). The tokens %s and %i etc are replaced by the variables and functions called after the double quotes, in order.

Use the assembled string to write to note files, and echo to system log as well, why not.

All that done, I then just use the already existing functions to set the actual warrants, and call it a day.

For pardon we do all that stuff backwards. Kinda.

Tandrael
Posts: 92
Joined: Mon Jun 25, 2018 12:02 pm

Re: Itsy's Janky Code Blog

Post by Tandrael » Sun Jan 16, 2022 11:36 am

I’m not a coder, but it’s super cool that you’re posting stuff like this. I have to say it’s been really great getting an inside look on what the staff thinks and does. Thank you Itesh, Elysia, Feneon, and all the other staff for everything.

Itesh
Posts: 828
Joined: Sat Feb 14, 2015 4:59 am

Re: Itsy's Janky Code Blog

Post by Itesh » Sun Jan 16, 2022 6:30 pm

So I didn't do the justice clan function in the way I expected. Instead, I hard-coded the notion of a justice clan into the flags of the clan itself (you know, like a clan being 'secret' or 'shadow', that kind of thing) and checked for that. And then flagged all the justice clans. That took half an hour or so.

I was reminded that I needed to create a new NO_CLAN_POWERS flag, to turn off the ability to warrant when someone can't be trusted with the shinies, and did so - this was just a case of overwriting flag SPARE1 - no real work at all.

Then I wrote most of the sanity checks for the new warrant command, and got as far as assembling the string for the system log, before getting sidetracked into a discussion with Aureus about flamethrowers and the problems with darken (don't dereference null pointers, mmkay). Let us be clear that I would not have caught that problem in a million years.

A few notes: the string needs to make the numbers clearer as room IDs, and also make it clear what kind of warrant is being levelled - just because it's Anor doing it doesn't necessarily mean it's a Whitecloak warrant as he could declan. So I need to have a think about how to do that. And assemble different strings for the note file of the warranter and warrantee, in order to save space where possible and preserve maximum readability. And impose a word limit on the reasoning string.

That's taken me to 11.30, and if it seems like I haven't achieved a lot, it's because I was using wood filler downstairs on a bit of plywood that covers a gaping hole in my house until 9.30. C'est la vie!

Fin.

Detritus
Posts: 282
Joined: Thu Aug 06, 2020 8:22 am

Re: Itsy's Janky Code Blog

Post by Detritus » Sun Jan 16, 2022 9:07 pm

I've been doing it wrong, no wonder it doesn't work sometimes!
parson <playername> <equally sensible reason>

Itesh
Posts: 828
Joined: Sat Feb 14, 2015 4:59 am

Re: Itsy's Janky Code Blog

Post by Itesh » Fri Jan 21, 2022 5:10 pm

I only had 20 minutes today so just worked on some infrastructure for the warranting command. Essentially, we needed to find a way to get from the concept of 'your clan'->'what warrant it is you're actually setting'; this is not as obvious as it first sounds, as the warrants are tied to city identities and are in no fashion at all linked to the clan structure. Mobol usually gets round this because it uses an imm command with exceeding specificity - one command that takes a city name, so for instance, Rodrivar Tihera will have mobol that states 'Tear'.

Anyway, long story short, wrote a function that accepts a pointer to a player and returns the relevant bit value of the warrant they should be setting - purposefully entirely ignoring SHADOW, MC, and MURDERER warrants as something to be decided on later. This is useful since we can now not only use this to set the correct warrant with a minimum of fuss in our warrant command, but also to use the same functions that print your warrant list on stat to translate the bits into printable strings (which we wanted).

Itesh
Posts: 828
Joined: Sat Feb 14, 2015 4:59 am

Re: Itsy's Janky Code Blog

Post by Itesh » Sat Jan 22, 2022 2:22 am

In another stolen moment this morning, and largely sous bebé, as the French categorically do not say, reviewed ("reviewed" lol) and approved eight out of nine of Aureus's most recent fixes. The ninth, which I'm certain is also correct, I need to spend more time on with a screen larger than a phone screen to work out why it's fixed the issue.

In case you're wondering, yes - Aureus tends to fix things faster and more often than me. Usually before I'm fully aware of them as problems.

I usually work on new features - it's substantially easier to write your own new code to Do The Thing than it is to work out how someone else's works.

Edit: I do keep telling Aureus to take some time and work on something fun but apparently none of you bastards deserve the genius that spews from his brain hole or something like that. Rude, I call it. I'm offended on your behalf.

Itesh
Posts: 828
Joined: Sat Feb 14, 2015 4:59 am

Re: Itsy's Janky Code Blog

Post by Itesh » Sun Jan 23, 2022 5:39 pm

Tonight I head back onwards with the warrant command. Round 1 was mostly sanity checks and assembling a syslog string, round 2 involved some support functions to link the justice 'area' (e.g. Tear) to the actual clan (e.g. Defenders of the Stone) - because astonishingly (lol) that is not an actual in-built Thing.

Today:

First, having assembled our string that is going to the syslog, we add a couple of characters to make it clear to readers that the number supplied is the number of the room in which the players concerned are, rather than any other random number we could be delivering (just using RM for this).

Using one of the support functions I wrote yesterday or the day before, we can now add the city string (e.g. TEAR, EMPIRE, whatever) to the string.

Old Version:
WARRANT: Itesh (1234) warranted Vampa (1234): No Reason Given
or
WARRANT: Itesh (1234) warranted Vampa (1234): Surely a very sensible reason, like being a witch

New Version:
Tear Warrant: Vampa (Rm 1234) warranted by Itesh (Rm 1234): No Reason Given
or
same as the above with a reason, really.

Vampa has asked for a mob name and number if a mob uses this command - luckily, the thing that fetches player names fetches mob names (it's not the same field) if the relevant player is mob, so this already happens, but we'll need to add a string to handle the mob number using ternary operators.

With that in mind, our string is now looking a bit like this:

Code: Select all

sprintf(buffer,"%s warrant: %s (RM %i) warranted by %s %i (RM %i): %s", city string, victim name string, victim location , player name, (is npc) ? mob number : "" , player location,(!*arg2 ? "No Reason Given" : arg2)); 
For reference, ternary operators give us a way to write if/else checks inline, so:

Code: Select all

(x < y) ? do a thing : do a different thing
is the same as:

Code: Select all

if ( x < y ) {
    do a thing;
}
else {
    do a different thing;
}
I am getting hugely distracted by videos of Osiris New Dawn.

I'm not entirely sure that I can send "" to %i, which is generally the format specifier for an int, but I guess I'll see whether this compiles or not.

...and on consultation it turns out you can't. So what I have to do now is fetch the mob's ID number separately, and then do a bit more processing to turn it into a string rather than a number, and then I can do the thing where we print "" to just print nothing. That made sense to me. Hush.

...and instead of that I'll be doing an if/else where:

Code: Select all

if ( player is an NPC)
    print one variant of the whole thing
else
    do the other far less complex thing
That gets sent the system log. I'm filtering that to level 102, which is the lowest level at which an Imm might be watching for the misapplication of justice. Justice clans are commonly given to newbie Watchers, as with the exception of maybe the Lancers they are usually low effort and therefore a good starter.

With that done, I use an existing function that takes a bitvector as an argument to fetch the actual name of the warrant and pass that to the immortal warrant function to handle all that jazz, and assemble strings for the note files that are slightly different for player and victim.

I'm sure this will compile fine.

Shut up.

Yeah so adding this to the actual command table would help, jesus.

Error Time:
  • Yeah, so, if you don't close your comments correctly you can delete the front half of the clan table.
  • 'is' is not a C reserved word so having it randomly written in your code outside a defined string is... not great.
  • When using a function that sends text to a character it's always best to include a pointer to that character so the text can actually be sent. And also a semi-colon. Pleb.
  • I find it hard to believe that the concept of the mob index is not declared in this scope and yet here we are - apparently it is not. I declare it in this scope. Problem solved.
  • If you write yourself a todo list in the comments, but do not actually comment out the word todo, the compiler will decide that todo has not been declared in this scope. This is the stupidest thing I have done today. Comment out todo, problem resolved.
  • error: operands to ?: have different types ‘int’ and ‘const char*’. Okay fine, I'll admit that sounds serious, what stupid thing have I done? Okay remember I wondered if you could assign "" to %i? Yeah you really can't, but I already did this a different way so resolving this just involves deleting some stuff I should already have deleted.
And we're compiled! SEE, NO ERRORS.

A quick boot up later and I load one imm and two test mortals into the game and do some commanding.

Unclanned:

Code: Select all

* HP:Healthy MV:Fresh > warrant testtwo they are a witch
Arglebargle, Glop-glyf!?!
Okay fine, clanned Wisdom:

Code: Select all

* HP:Healthy MV:Fresh > warrant testtwo being vampa
You lack the authority to issue warrants.
Okay fine, now I'm a Companion:

Code: Select all

* HP:Healthy MV:Fresh > warrant vampa for personal hygiene reasons
You have insufficient rank to issue warrants.
So Itesh forgot to give me a rank but now its 3:

Code: Select all

* HP:Healthy MV:Fresh > warrant vampa
Warrant who?
That's fair, Vampa isn't logged in:

Code: Select all

* HP:Healthy MV:Fresh > warrant testtwo for some spurious crap
Usage:  <player>  { andor | whitebridge | tar valon | baerlon | fal dara | maradon | thiefbane | cairhien | whitecloaks | red eagles | wolves | shadow | male channeler | empire | murderer | bandar eban | illian | mayene | tremalking | kirendad | far madding | tanchico }
So obviously this isn't working. The function that turns our warrant bitvector into a string is perhaps not working, so I'm going to add a quick print line to reveal exactly what's happening here.

Code: Select all

* HP:Healthy MV:Fresh > warrant itesh shenanigans
buf2Illian 
So what's happening here is probably a case issue - as part of a normal command, 'Illian' would have been lowercased. My supposition is that if I take the time to lowercase that first 'i' this will probably work.

In the end, I also needed to trim trailing spaces off the string - the function I had stolen to convert the bitvector to a string was designed to print multiple words and was inserting a space after each one.

Finally:

Code: Select all

* HP:Healthy MV:Fresh > warrant testtwo she smells
Warrant issued for Testtwo by Illian.
Itesh saw this:

Code: Select all

* I105 HP:Healthy MV:Fresh > 
[ Illian warrant: Testtwo (Rm 65) warranted by Testone (Rm 65): she smells ]
Testone's notes say this:

Code: Select all

 1. the Guardian  (100)  Sun Jan 23 2022
  WARRANT from Illian in rm 65 against Testtwo in rm 65: she smells
...and Testtwo's notes say this:

Code: Select all

 1. the Guardian  (100)  Sun Jan 23 2022
  WANTED by Illian in rm 65 from Testone in rm 65: she smells
With that, and the fact that I temporarily went mad and confused \0 and \n during string processing, it's probably time to stop this.

Fin.

Post Reply