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

Re: Itsy's Janky Code Blog

Post by Itesh » Fri Feb 18, 2022 7:30 pm

Taking a slight diversion sideways today, we were discussing the idea of armors having resistances and vulnerabilities. I knocked this together in python to quickly simulate some hits and how the resistances would affect survivability. It's not very efficient and I more or less forgot to include abs, or the ability to change the percentages on the resists/vulns, but for about 45 minutes' work it's good enough. And, because it's not actually the mud code, or indeed even in C (I shudder at the thought of doing this in C...), I can show you the code for it!

Code: Select all

# a simulator to do some fake combat dice rolls to test survivability
# with new resist/vulns system

import dice

current_hitpoints = max_hitpoints = int(input("How many HPs do you want: "))

dmg = input("How much damage do you think you're doing? (xdy):")

# save a series of rolls - more than we need:
print("Storing 100 damage rolls...")
rolls = []
for num in range(100):
    num = sum(dice.roll(dmg))
    rolls.append(num)

# append one final massive value to make sure we die with no issues
rolls.append(max_hitpoints)

rounds = 0
standard_rnds = 0
full_vuln_rnds = 0
half_vuln_rnds = 0
full_resist_rnds = 0
half_resist_rnds = 0

# First, a standard set of rounds
lowest_dmg = rolls[0]
#print("\n1: Standard combat round, no modifiers:")
for roll in rolls:
    current_hitpoints -= roll
    standard_rnds += 1
    if current_hitpoints < 1:
        #print(f"You lasted {standard_rnds} rounds before dying.")
        break

current_hitpoints = max_hitpoints
# Full Vuln - 25%
lowest_dmg = rolls[0]
#print("\n2: Full Vulnerability - 25% extra damage:")
for roll in rolls:
    current_hitpoints -= int((roll/4) * 5)
    full_vuln_rnds += 1
    if current_hitpoints < 1:
       # print(f"You lasted {full_vuln_rnds} rounds before dying.")
        break

current_hitpoints = max_hitpoints
# Half Vuln - 12.5%
lowest_dmg = rolls[0]
#print("\n3: Half Vulnerability - 12.5% extra damage:")
for roll in rolls:
    current_hitpoints -= int((roll/8) * 9)
    half_vuln_rnds += 1
    if current_hitpoints < 1:
        #print(f"You lasted {half_vuln_rnds} rounds before dying.")
        break

current_hitpoints = max_hitpoints
# Full Resist - 25%
lowest_dmg = rolls[0]
#print("\n4: Full Resist - 25% less damage:")
for roll in rolls:
    current_hitpoints -= int((roll/4) * 3)
    full_resist_rnds += 1
    if current_hitpoints < 1:
       # print(f"You lasted {full_resist_rnds} rounds before dying.")
        break

current_hitpoints = max_hitpoints
# Half Resist - 12.5%
lowest_dmg = rolls[0]
#print("\n4: Full Resist - 25% less damage:")
for roll in rolls:
    current_hitpoints -= int((roll/8) * 7)
    half_resist_rnds += 1
    if current_hitpoints < 1:
        #print(f"You lasted {half_resist_rnds} rounds before dying.")
        break

print(f"Combat Simulation Results ({dmg} damage) ({max_hitpoints} HPs):")
print("Full: 25% --- Half: 12%\n")
print(f"Full Resist:\t {full_resist_rnds} rounds.")
print(f"Half Resist:\t {half_resist_rnds} rounds.")
print(f"Standard:\t {standard_rnds} rounds.")
print(f"Half Vuln:\t {half_vuln_rnds} rounds.")
print(f"Full Vuln:\t {full_vuln_rnds} rounds.")
Output from that looks something like this:
How many HPs do you want: 280
How much damage do you think you're doing? (xdy):7d6
Storing 100 damage rolls...
Combat Simulation Results (7d6 damage) (280 HPs):
Full: 25% --- Half: 12%

Full Resist: 16 rounds.
Half Resist: 14 rounds.
Standard: 13 rounds.
Half Vuln: 11 rounds.
Full Vuln: 10 rounds.
edit: forgot to say - delighted to discover there is a dice module for python so I had to do literally no work to translate '3d6' into an actual number. Brilliant.

extra edit: and because obviously I didn't read the instructions probably I did this:

Code: Select all

num = sum(dice.roll(dmg))
...where I actually could have done this:

Code: Select all

num = dice.roll(dmg+t)

Raiste
Posts: 65
Joined: Tue Oct 20, 2020 6:39 pm

Re: Itsy's Janky Code Blog

Post by Raiste » Sat Feb 19, 2022 2:11 pm

Code: Select all

from math import ceil

HP = 280
X = 7
Y = 6

MODIFIERS = [
    ("Standard", 1.0),
    ("Full Vuln", 1.25),
    ("Half Vuln", 1.125),
    ("Full Resist", 0.75),
    ("Half Resist", 0.875)
]


def E(x, y):
    # Expected value of a XdY damage roll.
    return x * ((1 + y) / 2)

def modify_E_damage(x, y, modifier):
    # Expected value of a XdY damage roll given a static modifier.
    return modifier * E(x, y)

def n_hits_to_die(hp, expected_damage_per_hit):
    # How many hits until hp <= 0 on average.
    return ceil(hp / expected_damage_per_hit)


for MODIFIER in MODIFIERS:
    print("{}: {}".format(MODIFIER[0],
                          n_hits_to_die(hp=HP,
                                        expected_damage_per_hit=modify_E_damage(x=X,
                                                                                y=Y,
                                                                                modifier=MODIFIER[1]))))

python -i itesh.py
Standard: 12
Full Vuln: 10
Half Vuln: 11
Full Resist: 16
Half Resist: 14

Enjoying your blog. Please find attached the power of averages. You've motivated me to update my website and finish my full combat simulator. Cheers.

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

Re: Itsy's Janky Code Blog

Post by Itesh » Sat Feb 19, 2022 3:34 pm

Mathemagician and heretic!

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

Re: Itsy's Janky Code Blog

Post by Itesh » Sat Feb 19, 2022 3:37 pm

Today took another brief sidestep and copied the imm command that lists all weapons and remade it so it lists all armor, which seemed useful.

Issues:
1. a printf statement with like 8 arguments is inherent shitty to read and modify
2. had to do some bracketing and explicit casts to short to make DB and PB minus figures show up correctly and not as 65 thousand and something.
3. Initially managed to print abs as 0.55 which wasn't brilliant, but was a relatively easy fix.

Tomorrow I'll clean up a little and double test and then probably call it good.

Prykor
Posts: 180
Joined: Thu Jan 06, 2022 8:56 pm

Re: Itsy's Janky Code Blog

Post by Prykor » Sun Feb 20, 2022 4:21 pm

Itesh wrote:
Sat Feb 19, 2022 3:37 pm

2. had to do some bracketing and explicit casts to short to make DB and PB minus figures show up correctly and not as 65 thousand and something.
Gotta love integer roll over!

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

Re: Itsy's Janky Code Blog

Post by Itesh » Fri May 20, 2022 6:04 am

Well, it's been a whikle since I've done this, hasn't it - largely because I spent half a year, oh, not doing anything except being lazy and dropping gnostic comments into Staff-level policy discussions.

So, what are we doing today? There are some things we have to accept as truth:

1: I have a lot of projects
2: I cannot remember what those projects are
3: There are bunch of things in the approval queue...
4: ...some of which Aureus has rejected because he's just mean and...
5: I largely cannot remember what they are either, and...
6: I *could* do that thing Austin has been bugging me for well over a year, which I did actually do but also got it tangled up with like three other things.

So obviously I'm going with number 6. Austin asked me to create a kind of Watcher Log - similar to the system log we can turn on, but seperate, to avoid the horrific spam, that will echo RP to Watchers to allow them to reward it better. Seems like a plan, and like I said, I actually knocked that part out pretty quickly a year ago but then in the same update started coding the esay and pose commands, which got more complex. Flash prefers single issue requests, so I'm going to pull the Watcher Log out of that whole mess, tidy it up, and submit it.

To do this, since the original branch is well out of date, I'll open a brand new clean branch, and then just selectively copy paste bits in from GitHub.

How this is basically going to work is:

1: Enter command (almost certainly going to be watch with an imm command symbol)
2: A bit in your miscellaneous memory field gets toggled on
3: Watcher log therefore echoes stuff to you.

Which is a near copy of how the system log works except that your system log saves over boots and this will turn itself off each time. The reason for this is that we are essentially out of preference flags - like, say change autospam 1, that kind of thing - which are saved. Misc flags are not saved, but I don't think it detracts from the whole thing to make a Watcher opt-in to the info.

Obviously I have to remember how Visual Studio Code works first - what a great start.

The first we're going to do is change the command - in the old version, it's lumped into the existing 'change' command, so you would type 'change watching' (or maybe 'change watching on', I can't really remember and it's not important anyway). In the new version, I just want an Imm to type /watch and be done with it. This is pretty much just a case of entering the new command into the command table, and linking it to the function I wrote to do the actual bit toggle, so easy peasy:

Instead of:
change watching on
blah blah blah code checks
call toggle_watching_bit()

we just have one line in a table that reads:
command 435(ish), /watch, toggle_watching_bit(), min level etc etc etc

That does mean that we need to move some safety checks into the toggle_watching_bit() function - is this person an Imm? Are they a Watcher? That kind of thing.

And now, a word from our sponsors, while Itesh actually does that work!

Fed up with the daily grind? Find yourself writing passive aggressive forum posts in your dead-end job before you go back to your parents' basement to sniff lines of Cheetos off the chest of you full-size body pillow? ITESH HAS NO ANSWERS FOR YOU but really just try and knock off the passive aggressive stuff, that dung gets old.

Have temporarily (I hope!) misplaced compare function on GitHub, which is going to make this a lot more annoying. While I ponder this (not my orb), quickly throwing an eye over and approving all of Aureus' outstanding requests. This is a nonsense, of course, because he's a lot better than me, but maybe it'll be educational.

So, just quickly: maths hurts my brains, a lot. I lack the ability to glance over anything too fucky without my brain panicking and shutting down. Aureus, however, tends to write dung like this: x = MIN(MAX(2,green/34),MIN(MAX(hit points / time),x/30)) ...
... and that dung is why he doesn't get his stuff approved quickly. So, yeah. Sorry about that.

Worked out how to get that compare back. So, we add the definition of watching into the file where that kind of crap is kept, with a value of (1 << 6 ) or thereabouts, stick that also in the place where those values are translated into strings, and add the function that toggles that bit on and off. And that's the *basic* structure of we need in the background.

What remains is the Watcher Log function itself, which is getting added to a file full of utilities. In order to make it work, we also need to #include the header file for clans (because by definition a Watcher is in the Watcher clan), and add some external functions for genders and class abbreviations into the scope of this file so we can use them. And the function itself is basically a copy of syslog, with some stuff stripped out that I don't need. So I don't really need to worry about *how* to send to all relevant Imms, because that code is already there. Whoop whoop!

How this works is this: basically anything you do to RP generates a string, eventually, somewhere. So all we do is, after that string has been sent to all relevant players, be that in the room, or direct to one person or whatever, we send that string to our watcherlog function:

watcherlog(string, pointer to the character doing it, int value for TYPE)

We want a pointer to the character because later on, we'll want to perform some checks on them. The TYPE value is just so we can classify stuff for the log - so for instance, type 1 might be SAY, type 2 might be EMOTE, etc.

Par example:
emote = TYPE 2
arg = "Itesh holds up his hand and wiggles it."
watcherlog(arg, rper, 2);

The general format of the log is:

// General style: [ Itesh - Lvl 104 M Cha - clan - R: (number)]
// [watch_type: string ]

Par example:

[ Itesh - Lvl 104 M Cha - Watchers - R: (10001)]
[EMOTE: Itesh holds up his hand and wiggles it. ]

[ Itesh - Lvl 104 M Cha - Watchers - R: (10001)]
[SAY: Itesh says 'Ideally we'd overthrow Vampa and install someone semi-competent' ]

[ Itesh - Lvl 104 M Cha - Watchers - R: (10001)]
[ASK: 'Surely we can put Elysia in charge?' ]

[ Itesh - Lvl 104 M Cha - Watchers - R: (10001)]
[POSE: Itesh props up the bar like a true revolutionary. ]

[ Itesh - Lvl 104 M Cha - Watchers - R: (10001)]
[ESAY: Itesh says treacherously 'firetruck it, let's depose Aureus and lower the standard of coding' ]

We can use the pointer "rper" for all sorts of checks like, is this person and NPC? Because we don't care what they do, for the most part. And we can also use it to stop things that Darkfriends say from going to lower level Imms. Because we care.

Now, you might reasonably ask, 'But what about muh freedoms-er... privacy!' - well, sorry. You've never had privacy, not in all the time you've been here. Things you say and emote out loud in rooms, might have invisible Imms. Things you ask mobs are echoed directly to the system log. High level Imms have access to your tells via a circuitous route. So on balance, this doesn't really change anything in that regard.

This work is now basically done (including a small fix to the part that filters Darkfriend RP, because I should have done a continue there not a return) and all that needs doing is the insertion of the function calls inside all the RP commands, and the creation of the actual command table entry. And that, I'm going to do later, because ideally I should stop wearing pyjamas and actually parent my children.

Elysia
Posts: 7908
Joined: Sun Feb 15, 2015 1:29 pm

Re: Itsy's Janky Code Blog

Post by Elysia » Fri May 20, 2022 8:01 am

And all this because I proposed doing a day, or an imm announcement/title where we go "Hey, we/ I have some time, if you're RPing over globals (e.g. so we can -see it happening-), you might be rewarded. Which brought this old idea to the forefront again.

And yes, said idea is going to happen whether this command is in or not, but RL's been kicking my ass.

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

Re: Itsy's Janky Code Blog

Post by Itesh » Sat May 21, 2022 9:34 am

Astonishingly I managed to find the time to sit down the next day. The children are playing in the garden and while there's a fair-to-decent chance one will impale the other on a rake, or a stray rabbit, or perhaps a ravening hive of wasps will make off with the both of them I can maybe spent a pre-disaster hour working on code.

Obviously, instead of finishing yesterday's work I'll instead look at a request from Maddoc, who wants to follow people when they flee. This is for Imms, not mortals - I would say *obviously* (LeviOsAAAAARRR) except it wasn't obvious yesterday when I chatted to Aureus about it and he thought we were merely changing the fundamental landscape of PK on a whim (again).

There's a flee function, of course, and if we're lucky it won't just call the 'simple move' function. Here's a fun thing about 'simple move' - it's one of the longest, most complicated bits of code there is. It's not at all simple. It's not pleasant to read. It's extremely fuckily formatted.

I mean OF COURSE flee just does a bunch of checks and then calls 'simple move'. OF COURSE. Nine Hells of Sithaer. That's right, you get Mistwraith quotes today.

Urgh. Well, I suppose the plua side here is that the standard movement code already includes everything we need for following someone, it just means I need to wade through the knee-deep morass until I find the right place to add an exception.

...and the above turned out to not be true. It's still doable, I just think the way this is going to go is to locate the flee success message in either 'flee' or 'move' and immediately after that, iterate through followers, do a level check on them and if they're above or equal to 100 to extract from the current room and load into the new room. And now we're moving on, because this was mildly annoying!

So yesterday I moved over all the existing functions, definitions, constant character arrays to lable the strings and a few other bits and bobs. Now I need to add the actual command, which is just an entry in a table, and increment max commands.

Before that it occurs to me that since this is now a command, it might be good to change the toggle into a standard command format, which is mostly just a case of changing this line:

void do_watching(struct character data * ya_de_blah) // some of this intentionally obscure

to this:

ACMD(do_watch)

...and literally nothing else. It really wouldn't have mattered either way, but this has some consistency and also means if we have to expand this command in the future it'll be very, *very* mildly easier. I mean, infinitisemally easier.

While entering this all into the command table I remember that there is already a watch function, so I need to rename this again - which is fine, because a lot of Imm commands are prefixed with 'wiz' anyway. And then it occurs to me that since this is now an Imm command I should probably move it to the Imm command file and anyway, that's why I'm achieving nothing today.

So eventually that all gets done and at this point I really like getting a compile under way would probably be a good idea because this has been haphazard as all hell. And it would be good to know if I can just get the basic 'turning it on and off' stuff nailed down before worrying excessively about formatting and reporting. Of course, it's been 3 months since I turned the virtual machine on, so there's every chance it has simply stopped working. It does that, periodically, and then I have to spend hours hunched over Google like a teenager with a woeful sock just to get it going again.

Astonishingly it does actually compile. I don't know why this surprises me so much. Observe this horrific false start where I am momemtarily convinced everything is fucked:

* I105 HP:Healthy SP:Bursting MV:Fresh > do me a solid watch // this is obviously not the command
Arglebargle, glop-glyf!?!

* I105 HP:Healthy SP:Bursting MV:Fresh > who
Players
-------
%[Imm Imp] Itesh the Creator (Wizinvis Imp)

1 of 1 chars, 1g 0h 0o 0t 0m 0d 0s 0a 0gm.

* I105 HP:Healthy SP:Bursting MV:Fresh > // Idiot enters the command to clan
Itesh's clan set to Watchers.

* I105 HP:Healthy SP:Bursting MV:Fresh > do me a solid watch
You will now watch Roleplaying.

What happens next is that I need to insert function calls to the Watcherlog after says, asks, emotes and the other thing I have temporarily forgotten. Which I could probably work out, but which I'm obviously going to just copy off the old version of this so as to avoid any excessive and unncessary Thinkery.

It's probably bad practice to just dump numbers into the function instead of defining out the arguments - so I do that for the RP types quickly:

#define RP_TYPE_EMOTE 0

so instead of:

watcherlog(argument, person, 0)

...you have:

watcherlog (argument, person, RP_TYPE_EMOTE)

...which is very mildly clearer.

And so with another brief burst of compilation, including only one small error wherein I failed to declare an external function in a file, we are once more up and ready for testing.

* I105 HP:Healthy SP:Bursting MV:Fresh > spy egregiously on people
You will now watch Roleplaying.

* I105 HP:Healthy SP:Bursting MV:Fresh >
[ Testone - Lvl 51 m Hun - no clan - R: 1020 ]
[ SAY: $n drones 'Hello there!' ]
* I105 HP:Healthy SP:Bursting MV:Fresh >
[ Testone whispers to the palace servant 'I'm so alone!' ]
[ Testone - Lvl 51 m Hun - no clan - R: 1021 ]
[ WHISPER: Testone whispers to the palace servant 'I'm so alone!' ]
* I105 HP:Healthy SP:Bursting MV:Fresh >
[ Testone - Lvl 51 m Hun - no clan - R: 1021 ]
[ EMOTE: looks around for the nearest sad Novice. ]
* I105 HP:Healthy SP:Bursting MV:Fresh >
[ Testone - Lvl 51 m Hun - no clan - R: 1002 ]
[ ASK: Testone asks an elite Lion Warden 'Can you just reach into my pocket a second...' ]
* I105 HP:Healthy SP:Bursting MV:Fresh >

So, basically fine. Mild issue with SAY - what's happening there is that we're getting the string before it's sent to the act function, which swaps $n for a relevant name. We can do a bit of string processing in the log itself though, and cut that out.

...you know, some other day.

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

Re: Itsy's Janky Code Blog

Post by Itesh » Wed May 25, 2022 7:18 am

So I have an hour while the baby sleeps, and I'm going to try and tidy this up a bit:
[ Testone - Lvl 51 m Hun - no clan - R: 1002 ]
[ EMOTE: Testone lends a hand to a nearby cat. ]
For a start it's missing race info, and also it's a bit bulky. And given I added the player's name to all the social strings, it might be extraneous to actually keep mentioning it.

Without coding anything quite yet, I squiff around for a while trying to find a format I like:
[ L51 m LS Hun: no clan R: 1002 ]
[ EMOTE: Testone lends a hand to a nearby cat. ]
[ Lvl 51 m Hu Hun - no clan - R: 1002 ]
[ EMOTE: Testone lends a hand to a nearby cat. ]
[ EMOTE: Testone lends a hand to a nearby cat. (M 51 Hu Hun - Defender of the Stone R1002)]
I can't really decide on any of them, so this seems like someone else's problem: I dump options into Imm chat and solicit opinions like a cheap intellectual floozy.

And now we play the waiting game.

While I wait, I consider making lunch. But to be honest, I've been feeling sad for days and I'm not sure I can be bothered to do anything more constructive than stealing the biscuits my wife bought for the children - so it might be best if I stayed upstairs and didn't do that.

Did you know that Aldi make a fake Penguin, called a Seal bar? We're living in the future.

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

Re: Itsy's Janky Code Blog

Post by Itesh » Wed May 25, 2022 7:44 am

Follow-up note to the above, I suppose:

Part of the reason it's so hard to provide a concise character info dump briefly and coherently is that there's no consistent way to denote clan affiliation. Or at least, no way that isn't going to involve a bunch of annoying work. Here are some of the options and why they're bad.

1: Clan ID
- Yes, each clan *does* have an ID, but no-one knows those, except for more than a handful off the top of their head. I mean I think Wisdoms is 39, but it might also be 49. There's a nine. Other clans? NFC. Tower is in the mid-thirties, that's all I got. We rarely need the actual numbers, except in mobol, and I guarantee you everyone looks it up in-game every time.

2: Just use the first word!
- Okay, first off, there's no just anything when it comes to string manipulation in C! But I guess we could do that. You'd have clan tags like 'Illian', and 'Defender', and 'White', and 'White, and 'White' and oh dear, this isn't working well. I guess we could add exceptions to split the White Ajah from the White Tower and the White Leopards but ugh.

3: You know you're going to end up doing a const * char array, don't you. And you'll have to address it with the clan ID like a pleb, too.
- Le sigh. Unfortunately this might actually be the best option. It's a really annoying job, though.

Here's how this works. You know you have variables, right? If I write:

Code: Select all

int x = 5;
I have declared that a variable exists, like a little box. That box has 'x' written on the side (and a label that says 'integers only!', and into the box I have placed the value 5. So far so good, right?

You also have arrays - which are just a ton of little boxes with the same name, and a number. Technically, they are all adjacent to each other in memory, but don't worry about that. It looks like this:

Code: Select all

int myArray[3];
Note that I didn't give it any values. Don't worry about that either. It has three members (or is the word elements?) and they are accessed thusly:

Code: Select all

myArray[0]
myArray[1]
myArray[2]
C starts counting at 0, so we have elements 0, 1 and 2. Three in total. Now, instead of writing real numbers, you can use a variable holding a number in the square brackets, like this:

Code: Select all

myArray[x]
...which will basically give you:

Code: Select all

myArray[5]
But Itesh, I hear you cry, Your array doesn't go up to 5! And you're right, it doesn't. That dung will crash. Touching memory you didn't say you were going to work with will kill the program, and is called a segmentation error. Everything will segfault and die. Boom!

Edit: And this is why people with very, very long trophies killed the game, for a period.

ANYWAY.

So the point is, I can do this:

Code: Select all

const char * clan_abbreviations[] = {
"GHAR",
"DHAI",
"CoL",
"HoL",
blah blah blah etc ... 
};
(You don't need to include a number in the square brackets if you populate your array when you create it)

And then do this: clan_abbreviations[CLAN ID NUMBER]. And that's fine, I guess, but there's 78 clans and the thought of actually writing that all out makes me want to cry.

Post Reply