26 Jun, 2014, Davion wrote in the 81st comment:
Votes: 0
We just finished a major refactoring. All do_functions have been moved to their own file, in their own sub directory (pysrc/commands). For development purposes these files are all tracked for modification. Once modified you can then reload the function and the code is life, with no game-state change. interp.py no longer contains a massive cmd_table. They were broken off into the individual files as well (so when the code is reloaded so are the definitions to it.) The cmd_table is still and OrderedDict. So to keep n,e,s,w,u,d,k,i,w at the top you leave a None entry after the definition. This will keep their order at the top. The rest will be loaded based on the file system I'd assume. It looks to be alphabetical.

Here's what our do_help now looks like. This is an example of how alias' now work, as well a basic default_arg to pass to them if need be. This is commands/do_help.py

import merc
import interp
import nanny

def do_help(ch, argument):
if not argument:
argument = "summary"

found = [h for h in merc.help_list if h.level <= self.get_trust() and argument.lower() in h.keyword.lower()]

for pHelp in found:
if ch.desc.is_connected(nanny.con_playing):
self.send("\n============================================================\n")
ch.send(pHelp.keyword)
ch.send("\n")
text = pHelp.text
if pHelp.text[0] == '.':
text = pHelp.text[1:]
ch.send(text + "\n")
# small hack :) */
if ch.desc and ch.desc.connected != nanny.con_playing and ch.desc.connected != nanny.con_gen_groups:
break

if not found:
self.send("No help on that word.\n")

POS_DEAD = merc.POS_DEAD
LOG_NORMAL = merc.LOG_NORMAL
IM = merc.IM

interp.cmd_table['help'] = interp.cmd_type('help', do_help, POS_DEAD, 0, LOG_NORMAL, 1)
interp.cmd_table['motd'] = interp.cmd_type('motd', do_help, POS_DEAD, 0, LOG_NORMAL, 1, 'motd')
interp.cmd_table['imotd'] = interp.cmd_type('imotd', do_help, POS_DEAD, IM, LOG_NORMAL, 1, 'imotd')
interp.cmd_table['rules'] = interp.cmd_type('rules', do_help, POS_DEAD, 0, LOG_NORMAL, 1, 'rules')
interp.cmd_table['story'] = interp.cmd_type('story', do_help, POS_DEAD, 0, LOG_NORMAL, 1, 'story')
interp.cmd_table['wizlist'] = interp.cmd_type('wizlist', do_help, POS_DEAD, 0, LOG_NORMAL, 1, 'wizlist')
interp.cmd_table['credits'] = interp.cmd_type('credits', do_help, POS_DEAD, 0, LOG_NORMAL, 1, 'credits')


The way we have it setup, to add or remove commands from the game is simply adding or removing a file. The current setup has all the files in a single folder but we plan to use a subdirectory tree to organize them a little better.

The future game plan now is to do a similar thing to spells. Scripting seems to have popped up. Syn has discovered python can tokenize itself. The experiments ensue ;)

*Edit
Tenitively for a spell setup you'd drop this spell_acid_blast.py into the spells subdirectory and you'd have acid blast.

from merc import dice, DAM_ACID, saves_spell, TAR_CHAR_OFFENSIVE, POS_FIGHTING
from const import skill_type, SLOT
from fight import damage

def spell_acid_blast(sn, level, ch, victim, target):
dam = dice( level, 12 )
if saves_spell(level, victim, DAM_ACID):
dam = dam//2
damage(ch, victim, dam, sn, DAM_ACID, True)

skill_type("acid blast",
{'mage':28, 'cleric':53, 'thief':35, 'warrior':32 },
{ 'mage':1, 'cleric':1, 'thief':2, 'warrior':2 },
spell_acid_blast, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
None, SLOT(70), 20, 12, "acid blast", "!Acid Blast!", "")
27 Jun, 2014, quixadhal wrote in the 82nd comment:
Votes: 0
Yep, very good progress on this front. Once I get started on the mprog system (and the pyprog which syn will probably spearhead to replace it), the same technique can be used. Dropping all the special procedures and even standard NPC behaviors into modules this way will make it much easier for future game admins and builders to create highly customized NPC's with a minimal risk to the core systems.
28 Jun, 2014, Odoth wrote in the 83rd comment:
Votes: 0
Wondering if you have any basic stats comparing memory/cpu usage of Pyom vs regular C ROM?
28 Jun, 2014, quixadhal wrote in the 84th comment:
Votes: 0
At some point, we could probably compare them. I would assume it takes a bit more ram and a fair amount more cpu… but negligable on any semi-modern hardware. I mean, on my server my minecraft process takes more resources fully idle than 3 or 4 muds with players/bots running around on them.

Once we get stuff a bit more stable though, doing a few benchmarks will be fun. :)
28 Jun, 2014, Davion wrote in the 85th comment:
Votes: 0
Odoth said:
Wondering if you have any basic stats comparing memory/cpu usage of Pyom vs regular C ROM?


12699  0.1  0.1   6000  3788 pts/0    S    06:25   0:01 ../src/rom 3083
12789 6.2 0.8 24964 18456 pts/0 S 06:26 1:03 python3.4 ./pyom


I started them both up about 20 minutes ago.
29 Jun, 2014, syn wrote in the 86th comment:
Votes: 0
Anecdotaly I can say that response outside of the game loop (eg set ticks/updates happening on this format) is.. night and day vs C-ROM.

The python variant is LIGHTNING fast in comparison, to me.

More anecdotal info:

Running from pycharm IDE on windows:

Fresh boot: 31.3MB memory used
After nine hours: 35.3MB memory used
Avg cpu %.71 (of quad3ghz)
30 Jun, 2014, syn wrote in the 87th comment:
Votes: 0
I have been going through and trying to clean up the code a bit and with the default import format that pycharm uses:

from bla import bla, bla2, bla3, … etc

I have noticed it seems a tad more confused when files share similarly named global items, or for system imported items.

For instance, Living.py wanted to use fight.logger.info instead of a local variant, most files that use random.randint if a from bla import is used tend to try and pull random from the other file for some reason, and other issues along these lines.

PEP8 orders best practice like this:

import bla (func level): bla.something
from bla import somebla (easier, but less directly readable, along with potential issues as above)

it strong recommends to NOT do the following:

import bla, bla2, bla3, bla4

from bla import *

sometimes we may not be able to get around from bla import * but we should avoid it as much as possible. Even with the splits I have noticed a few files, as above, that are still getting a bit confused it seems.

Typically with the forced import bla bla.something this seems better, and I don't see as many conflicts.



Soo, to that end, if using pycharm, or some ide capable of this, I would suggest using, or switching to the auto import import bla bla.something format.

In pycharm go to File -> Settings -> Editor -> auto import -> Python -> preferred import method: import <module>.<name>

This will align your auto imports to that format.

Ultimately I think this provides more verbose, but much easier to 'follow' code, as as we get more complex in some areas this is probably a good thing.
30 Jun, 2014, Kelvin wrote in the 88th comment:
Votes: 0
Wildcard imports are almost always a bad idea. There's pretty much always a way to design more intelligently and avoid it altogether.
30 Jun, 2014, syn wrote in the 89th comment:
Votes: 0
Absolutely, the way that C-ROM was originally, nearly directly translated to python was very very bad in regards to cyclical imports, and there were so many items at first it was 'easier' to from bla import *

In practice, and reading, and watching what it does, I've been able to start cleaning up a lot of that, and with the recent breakup and smart re-homing of a lot of the core of the game between the three of us we have managed to get it on a much more solid footing.

beyond that, I just notice it has fewer hiccups using a straight:

import bla


if bla.something()

side benefit of being the easiest to read and know exactly what/where you are in anything.
02 Jul, 2014, quixadhal wrote in the 90th comment:
Votes: 0
Syn and I were talking earlier about the difficulties of using a serializer on the data structures in Pyom.

Mostly, this comes from the fact that most of the objects in Pyom have references to other objects, because that's the way the C implementation did things. The problem with having references (pointers) all over the place is that when you try to serialize anything, the serializer has to follow all those references and ends up spinning through many layers of recursion. Even if it can deal with circular references, it still ends up doing lots of extra work and usually runs out of stack space (too deep recursion).

I'm pretty sure I know how to solve this, but it's a fairly fundamental change to the way the game accesses data and will likely require a lot of code rewriting… none of it difficult, but most of it likely not easy to automate either.

I'll show a small example of what I mean here.

Currently, areas are stored in an array. The area data is loaded from the file into an area class object, which is then pushed onto the end of the array. Rooms are handled in much the same way. Within each room, there's an area variable which contains a reference to the same area object the room was loaded from. That way, you can find out the area a room belongs to, right?

Now, npc's work in much the same fashion, and they contain a variable called in_room, which references the room object they are currently in. So, to find out the area a given npc is in, follow the reference chain like npc.in_room.area, which gives you the original area reference. Nice and neat looking.

The problem, of course, is that all of these things end up pointing at each other, and the result it pretty much a big bowl of spaghetti that is incredibly annoying for a serializer to try to decipher.

The way (I see) to solve this issue is to rethink and refactor how all these objects are held, and how their relationships are kept.

Let's change areas so it's a dictionary, instead of an array, and we'll use the area ID as the key. Now, you can still iterate over the areas, but you can now also reference any particular area by its ID without storing a reference… the "area" property of the room object is now just an integer (or string) which is used as a key for the areas dictionary.

Now, you can also do the same thing for rooms, objects, npcs, resets, etc.. you just need a unique key. In the case of rooms, objects, and npcs… that can just be the vnum.

Now, let's look at some common bit of code to get at data. You have an object in the inventory of some npc and want to know what area the object came from AND what area it's now in.

In the current code, it probably looks like obj.area and obj.carried_by.in_room.area, right? Nice and neat, but all pointers that don't really serialize well.

You could rewrite that as areas[obj.area] and areas[rooms[npcs[obj.carried_by].in_room].area]

Certainly looks less attractive, but… area, carried_by, and in_room are all just key values. No references are involved, which means the objects in question are simpler and not dependent on one another. Easy to serialize!

For one more tidbit….

If we have dictionaries for areas, rooms, objects, and npcs, we know that those are for the template versions of each, which are loaded from disk and don't change. In the case of areas and rooms, that's all we need… good to go.

For npcs and objects though, we need something else. We also need dictionaries to hold the instanced versions. This is the golden path which will eventually let us fully persist the game state.

Let's say we have a global instance_id, which is just an integer and starts at 1. Every time you clone an object or npc, you bump that instanced_id up and assign it to the new thing you just made. You can keep seperate ones for each type of thing that needs instances, if you like.. it really doesn't matter.

Instances get kept in their own dictionaries, using that instance_id as their key.

So, in our above example, we'd have dictionaries for object_instances, and npc_instances.

When you clone an orc, maybe the orc is npc[4012]. You make a copy and save it in npc_instances[72661]. You give the orc a rusty scimitar, which was object[4033], and the newly cloned copy gets saved in object_instances[72662]. object_instances[72662].carried_by is going to be 72661. npc_instances[72661].carrying[0] will be 72662. Normally, this would be a circular reference, but here… it's just key values.

I think if we applied this (or some variation of it), we could eventually be able to just pickle the whole gamestate, restoring everything on reload. Even the player database could just be a dictionary called players with the key being their login name. There's no real need to load each player from a seperate file.

WileyMUD's player database, a few thousand flat files, takes up 18MB. That's 20 years worth of players. My machine has 16GB of RAM… I wouldn't care if my player database were 100MB.. RAM is cheap now.

It's a big enough endeavour though, that I felt getting some feedback would be good before diving into that particular rabbit hole. How long it might take, I have no idea… if it's too big, it might warrant a new branch… but whomever works on it will need to be sure to keep merging in changes from the other branch. Otherwise, it'd be a super nightmare of conflicts to try to pop in all at once. :)
02 Jul, 2014, syn wrote in the 91st comment:
Votes: 0
I fully support and endorse this course of action.

If the main game is determined best to stay more Diku like in this manner, I will make a true fork to side by side the game with these changes. For my own benefit at the end I'd like a truly persistent world!
10 Jul, 2014, Davion wrote in the 92nd comment:
Votes: 0
Well! We have been insanely busy since the alpha release. We have begun tackling some of the biggest pitfalls of the codebase. A few things that have been completed:

[subheader]Already Developed[/subheader]
CHAR_DATA has been removed and broken down into smaller classes. Character, is a PC, and Mobile is an NPC. These inherit Living which gives them common properties. Living is broken down into smaller subclasses so anything with a name/short_descr/long_descr can have common methods act upon them.

Bits got a nice shiny new interface. At the core, they're still the blazing fast, memory optimized bitwise operators, but on the surface, you have a boolean-like syntax.
if ch.act.is_set('nosummon nofollow autogold autoexits')
#True if all 4 bits are set.
if ch.act.is_nosummon:
#true if nosummon is set
if ch.act.is_set(PLR_NOSUMMON | PLR_NOFOLLOW |PLR_AUTOGOLD)
# True if all 3 are set.
ch.act.set_bit(['nofollow', 'nosummon', 'autoexits'])
#sets nofollow, nosummon, autoexits

At current you can pass bits an int of bits, a string of bits, or a sequence of bits and weather you're setting or checking them it works!

[subheader]Progs done Python[/subheader]
We have introduced PyProgs. We looked at ways to embed python into ROM but alas, it's too powerful to hand over to everyone so we wrote our own! We're created a pythonic mini language to power our progs. The idea is that people can transition from player, to builder, to coder a lot easier than with your classic rom. Here's the example script we're running. Note, it does not contain all the features.

if actor.perm_hit > 20:
if actor.perm_hit > 30:
actor.do_say("I'm pretty much god.")
else:
actor.do_say("I'm a beast!")
else:
actor.do_say("I'm a wimp!")
if actor.guild.name == 'mage':
actor.do_say("I'm a mage tho, so don't mess with me.")
elif actor.guild.name == 'thief':
actor.do_say("I'm a thief, hold your wallet!")
else:
actor.do_say("I'm not sure what I am.")
if not actor.act.is_set(PLR_CANLOOT):
actor.do_say("And I can't loot!")
for vict in actor.in_room.people:
actor.do_say(vict.name)


We plan to have tons of signals through the codebase to run these progs.

[subheader]Currently in development[/subheader]
All classes are being separated from their templates and put into instance classes of their own. All instances will get an instance ID. This doesn't sound like much, but it's huge. Currently in ROM only Mobs and Objs are instanced, now Mobs, Objs, Shops, and Rooms are all instanced (which means you can have the same dungeon with different players!)

On top of all this, we had a break through with the load procedures and the game now boots up in about 4-5 seconds (in the time I wrote this, Quix added a timer for the db. It's clocking in around 2-3 seconds. My bad!) in stead of 45 seconds! Enjoy!
12 Oct, 2014, Rarva.Riendf wrote in the 93rd comment:
Votes: 0
About mprogs I recently came into a little oversight from the original developpers.

The messaging system is inconsistent in behaviour.
as an exemple the mobile sending a message to the room will use the target character name when doing anything with him, even though other people in the room may not see this character to begin with. (hence needig to use someone instead of the name)

The thing itself is a mess. I've seen this exact same behaviour in lola. It is a problem in how mpecho (and most mpmethod work anyway), instead of using the regular act method that will check individually for all people what they should see, instead of the mobile deciding a unique message to echo to everyone.
80.0/93