27 Mar, 2014, quixadhal wrote in the 21st comment:
Votes: 0
I would suggest another point to address right at the start is if this would be a Dikuesque codebase, where all the code is directly written from the viewpoint of a game driver, or an LPesque codebase, where the driver would implement a language of its own, and provide event hooks from the driver into the softcode of the mudlib.

While we're talking about pseudocode, it still makes sense to describe which environment you're drawing up. If you look at an LPMUD driver, you'll see it has almost no game-related code at all, passing all that off to the mudlib itself, while dealing with sockets, file access, and ensuring softcode objects have resource limits.

OTOH, even a DikuMUD with scripting still has the majority of code in the driver, with scripts only acting on very game-specific hooks that can't be expanded without driver hacking.
28 Mar, 2014, Idealiad wrote in the 22nd comment:
Votes: 0
@plamzi, good points again, and I think you're right that some people might jump in as this gets more developed (or they'll just hear about it later).

@Nich, your theme/game preferences for this project align with mine, so I think we can run with that. I've taken the liberty of adding your name as a voting author to our reference article.

@quix, good point. I was not thinking about writing an LPesque server per se. But as I think we should shoot for a clear separation of loosely-coupled concerns we may write an LP-like by default.

Even though this is PseudoMUD, through all of it I'd like to focus on the idea of it being a 'real' mud with real gameplay. I think that'll help us to make practical decisions now. and be a better learning example for anyone else in the future.

To keep things clear I'm going to move forward in a double post.
28 Mar, 2014, Idealiad wrote in the 23rd comment:
Votes: 0
Here's where we're at. PseudoMUD is a fantasy H&S game. If we had the source tree in front of us, what would it look like? I'll take a stab at it so everyone can edit:

/PseudoMUD
- data
- areas
+ DeadlyDungeon
- help
help.txt
config.p ; *.p for pseudocode of course :)
- src
- driver
+ parser
+ network
- things
item.p
player.p
mob.p
room.p
thing.p
- game
+ abilities
+ combat
- commands
help.p
go.p
kill.p
say.p
+ effects
main.p
INSTALL
LICENSE
README

Obviously there are a lot of assumptions there, but it's something to add to and revise. I think once we nail down the basic structure we can tackle each topic on its own.

It occurs to me that it might be helpful to create a fake repository to make it easier to move files around and so forth…
28 Mar, 2014, Davion wrote in the 24th comment:
Votes: 0
You can use the tags in the articles section as a psuedo file repository if you want. They generate on the fly so should be easy to keep expanding.
28 Mar, 2014, Idealiad wrote in the 25th comment:
Votes: 0
Not sure what you mean Dav, do you mean we can create a pseudo directory tree by making new categories?
28 Mar, 2014, quixadhal wrote in the 26th comment:
Votes: 0
I would argue for moving the entire "things" category into "game".

That's part of the reason I like the way LPMUD is designed. While the majority of the games out there are room based, nothing in the driver ASSUMES that has to be true. The only thing the driver tracks (and that's optional) is containment relationship… so if your mudlib doesn't do it, you can let the driver track what objects are in the "environment" of other objects.

When a new socket connects to the driver, it makes a callback into the LPC "master" object and expects the mudlib to return an LPC object that will be known as the player object. From that point on, anything received on the socket is sent to a callback in that LPC object (which then handles parsing and dispatch), and anything that player object "writes" is sent out the socket.

I point this out, not because I expect you guys to follow that concept, but because I've found that an awful large chunk of the community makes the assumption that an LPMUD is just a mud with more advanced scripting, and that's not the case. It really is a different design, and it's worth considering how that works when designing a new system, IMHO.

As for the actual hierarchy, I would probably try to avoid making a distinction between players and npcs, and also between items and room. Actually, I'd treat all of those as items and simply make the distinction between living and non-living things. A couple ideas are below, just for thought.

The only real difference between a player and an NPC is the source of control. Most existing games even have a way for a player to take control of an NPC. One game idea I wanted to explore is the concept of persistence. In this context, it means your player character never just "disappears" when you log out, it just becomes an NPC which follows a strategy you set. The default might be to just sleep in an inn room, but you could set it to explore, to try to collect things, etc… if you're willing to take the risk.

For items, a traditional MUD makes a distinction between an "item" and a "room", but if you really look at them, they function the same way. A room is nothing more than a container which lacks an external description and has more than one exit. If you made rooms and items the same, the only thing you'd need to do is give each an "internal" and "external" description, as well as volume and lighting (if you use those). Then, if you wanted to cast a shrink spell and enter your backpack, it would just work without having to make special case code. A "soul gem" could actually be implemented as a full game area, which happens to be contained in an object people can carry around.
28 Mar, 2014, roguewombat wrote in the 27th comment:
Votes: 0
@quix, I like the idea, but a traditional MUD may also make a distinction between the canonical item definition and every individual instance of that item while rooms are just rooms. There may be 500 item 3's in existence, with any number of attribute variations among them, while there will only ever be one room 7.

I don't see any reason you can't just assume there's a room template and a unique room instance, though, and allowing for limited numbers of instances of things would be nice to have for items as well as rooms. In other words, in addition to internal vs. external considerations, I'd add something along the line of "Maximum # of instances", with rooms defaulting to 1.

Re: meta issues, can the pseudo-codebase as it's being developed live in markdown files on GitHub? Would make it so much easier to actually keep track of what's going on and to collaborate; for example, Idealiad says we can "edit" his general structure, but I don't see any way to edit his post other than just copying it and moving things around (or making proposals in plain text as quix has done).
28 Mar, 2014, Idealiad wrote in the 28th comment:
Votes: 0
@roguewombat, quite right, I was thinking of creating a repo as well. Davion had an idea of using tags here but I'm not sure what he meant.

@quix, do I read you right that parsing is also handled in the LPC mudlib, not the driver? Is the LPC master object in the driver or the mudlib? Or by definition do LPC objects always live in the mudlib?

I can see good reasons for moving game things out of the driver. In the hierarchy above I see item, player and room inheriting from thing (in some fashion, it could be inheritance or composition), so they're all 'things', but that's a good point about animate vs. inanimate.


edit: OK, I've gone ahead and created a repo: https://github.com/georgeoliver/PseudoMU...

And here's an updated tree:

C:\george\work\PseudoMUD>tree /F
Folder PATH listing for volume IBM_PRELOAD
Volume serial number is 3428-03C0
C:.
? INSTALL.md
? LICENSE.md
? README.md
?
????data
? ? config.p
? ?
? ????areas
? ? ????DeadlyDungeon
? ????help
? help.md
?
????src
????driver
? ????net
????game
? main.p
?
????abilities
????combat
????commands
? go.p
? help.p
? kill.p
? say.p
?
????effects
????things


Now we can use regular tools to move files and dirs around.
28 Mar, 2014, Nich wrote in the 29th comment:
Votes: 0
I like the idea of unifying PC's and NPC's, just because that has nice effects for debugging purposes.

About unifying items and rooms, I'm not so sure. To me, it comes down to this: What is an exit? If an exit is just a special case of item, contained in a room like any other item, then I think that the idea of a room just being a special case of container holds. However, if an exit is a property of room, I feel like rooms and items diverge too much in terms of their actual role to be worth merging (rooms do too many things that items don't do, and vice versa).

I can really see the benefit of merging PC's and NPC's, but the benefits of merging items and rooms seem more like a "neat" feature than a useful one. If shrinking spells, and carrying rooms around in your pocket are features of the MUD, this seems like a good way to get that functionality easily, but if we're not going to use those features I don't think there's a compelling reason to consider them in the design.
28 Mar, 2014, quixadhal wrote in the 30th comment:
Votes: 0
@Idealiad, yes the master object is an LPC object. The driver itself handles accepting connections, pulling data from them and sending it to LPC objects, taking data from LPC objects and sending it out a connection, accessing files, and compiling and running LPC code (which is interpreted). The driver also provides a set of functions that can be called from LPC, called "efuns" (short for external functions). Typically, you code things in the driver only when it's either used so frequently that you want the speed gain, or it's so complex to require the speed gain.

If you want to see, in more detail, how the LPMUD design works, wander over to lpmuds.net and poke around. The "Lil" mudlib is a barebones thing which basically shows the minimal mudlib needed to let people log in and issue commands… in the default case, you don't even get a username. I started poking at the idea of turning it into an I3 chat server, but got distracted… you can see that on github, https://github.com/quixadhal/SuckingMUD

@Nich, the idea isn't so much the special case of rooms vs. physical objects… it's more the concept of treating ALL in-game objects the same way. A room is a special case of an object which has an inventory, exits, and whose description and other attributes refer to its interior. A sword is a special case of an object which lacks an inventory or exits, and whose description refers to its exterior. A room doesn't have weight, but has size. A sword has weight and size. A goblin is a special case of an object that has an inventory, lacks exits, and whose description refers to its exterior. It has weight and size.

Instead of treating those as different things, why not simply say ALL objects have weight, size, inventories, descriptions, exits, etc. You can do this via inheritance, if you prefer to keep things compartmentalized… So an NPC might inherit from THING, CONTAINER, and LIVING. A sword might inherit from THING, and WEAPON. A room might inherit from THING, CONTAINER, and EXITS.

The idea I'm getting at is, when you say "look", you are really saying "show me the description of my environment", which is simply the interior description of the room you're standing in (usually). When you say "look at goblin", you are saying "show me the description of the thing called goblin", which is the exterior description of the goblin. If you were to want to code a remorhaz (giant ice worm), and give it an attack to swallow the player, treating all these things as properties means you don't have to special-case any code. Your ice worm NPC already inherits CONTAINER, it simply has in also inherit EXITS so there's a way in and out of it.

Obviously, there might be a few details to smooth out… like the idea that you shouldn't be able to enter or exit the ice worm of your own choice… but if you ensure it has that code, just like rooms, you never have to touch the command code for things like "look". You are inside the worm's belly, you do a look… it shows you the interior description of the NPC. Most NPC's won't have one, but they could. Same goes for objects.

@Idealiad, and yes… rooms are generally singleton objects. In LPC, there is the concept of a "clone". When you "load" an object, it compiles and loads a single instance of it. So, you might edit the room you're standing in and do "update /d/fooland/rooms/hallway", and it will reload the room. You can do the same thing for an NPC or sword, but you won't see anything happen… for those, you usually want to "clone /d/fooland/npc/orc" to make an instance appear in the room your'e standing in.

LPC also talks about objects from the programming perspective, which is why I try to use the term "item" when I mean a physical thing, as opposed to a chunk of code. A bounty system could be a singleton object in LPC, where it simply keeps track of what players have bounties on their heads. The driver doesn't really care. In the old days, the driver used to keep track of object containment, lighting, if something was a living thing, etc… modern mudlibs do more of that in the LPC layer. I think about half the mudlibs out there still use the driver for the idea of "living" vs "not living"… which in LPC terms means "has the function heart_beat() called on it periodically."
29 Mar, 2014, Nathan wrote in the 31st comment:
Votes: 0
I'm not familiar with LPC, but you could consider the flexibility of having parts of the code be in the driver OR the mudlib as an upside rather than enforcing total separation of everything to one side or the other although that would be more an individual use case thing.

I don't thing a room being a special case of item is generally a good idea. It's a cool idea and certainly enables things to be done that would otherwise would be difficult to accomplish, but at some level it breaks reality-level constraints that most games assume and rely upon. Assuming magic and teleportation are a thing, then I shouldn't be able to teleport into someone's insides without, say, being shunted away (D&D style) or seriously injuring them (by perforating them by virtue of physially being juxtapose. Sure, you can say "Well I shrink down to small enough…" but that's really a loophole explanation of sorts.

Also, something swallowing a player is generally fatal, since people aren't terribly sturdy and being subjected to the digestive system (which is supposed to help you eat things) generally isn't a pleasant or necessarily livable experience. So, the notion of swallowing someone as an attack and having them be able to get out again is another case of the above. If that's what you want for game whatever, but I don't think that's a general need.

—–

Also, if you merge PCs and NPCs far then it affects your approach to NPCs. For example, if NPCs give quests then you shouldn't store quest data on the NPC in that case unless you use the same space as a normal player would and code elsewhere checks to see if it's an NPC or a Player… which violates the whole purpose of merging since now you have to distinguish them again. I think this is definitely a matter of what, if anything, a Player/NPC can do that the other can't. If their possible actions/behaviors, etc are completely identical, then of course you should merge them.
29 Mar, 2014, quixadhal wrote in the 32nd comment:
Votes: 0
I'm not advocating allowing every object to be used (by the players) like a room. That would be silly. I am advocating making them function the same way, because there's really no reason NOT to do so.

From a coding perspective, if I want to see what's in the area around me, I want to grab environment(this_player())->inventory(). Always. I don't want to special case that so 99% of the time, it's a room, but sometimes it's not so I have to fiddle with code. Likewise, when I want to see what's inside something, I want to do object->inventory() and get back a useful result, even if it's empty. I don't want to have to write case statements that say if this thing is a room or a player or an npc or a backpack or some other kind of thing I haven't invented yet… then return inventory(), otherwise return an empty array… or worse yet, cause an error.

The separation of game mechanics into the softcoded mudlib is there to allow the game developer to focus on developing their game, and to allow things to be changed on the fly without needing to reboot or recompile things. In an LPMUD, it's normal for them to only reboot when the host machine's hardware changes. They can have uptimes of years, because the driver itself is only rarely modified. The LPC code is updated on the fly, and generally speaking, it's hard to lock yourself out in a way that can't be recovered from without a reboot. Even if you break the player object code, as long as you don't log out before fixing it, it's not fatal.

Addressing the idea of quest data is a good place to illustrate a difference of viewpoint between the LPMUD and DikuMUD developer's mindset, I think. I'm glad you brought that up. A C or C++ programmer is used to having to modify classes or structures on a regular basis, and as such, they tend to be fairly rigid in how they go about things. The classic distinction between an NPC and a player object is a good example.

In DikuMUD, both structures share a great deal of the same data, however some bits of it are used for different purposes (BAD!), and most DikuMUD codebases have a "pcdata" structure that's tacked in for player-specific things that NPC's usually don't have unless they're being controlled. This makes sense, because it's all hard coded into the driver, and so if you decide to add a quest system, you need to store the quest completion data somewhere. Typically, this ends up being another structure that's stuffed into a linked list whose head pointer is tacked into the "pcdata" structure. The load and save routines then have to be modified to load and save out that data, which has to have some textual encoding in the file system.

By contrast, an LPMUD is all code. The player objects, and the npc objects are instances of code, and as such they can have new variables added, modified, or removed on the fly. The driver typically offers a pair of efuns called save_object() and restore_object(), which serialize all the non-private variables in any given LPC object, allowing them to be written to a file. There's no need to write loading and saving code. There's no need to modify the driver to add things. If you want to add a quest system and store the data in the player's object, you can just modify the player object to have an empty array of quest data, and then have the NPC quest givers add their quest information directly to it. It gets saved automatically when the player is saved, via a single call to save_object(). Likewise when a player logs in, it gets restored.

Now, the cool thing to realize is that there's nothing preventing this from working on NPC's too. You can easily make NPC's save and restore state information in exactly the same way, and that allows you to give them memories. Maybe orc#817 fled and got away from you. If that orc saves state, it can remember that it hates you. Maybe you have a unique NPC that's a gate guard, and you want him to allow people to pass only if they've done something special… but only that particular guard. Instead of storing the data in the player, you could have the NPC remember who's on the approved list. Later, if the guard gets fired, the player may have to find another way in.

This is why I wanted to address the idea of hard-coding in the driver vs. softcode. It makes a big difference in how you design your game systems. The more things you put in the driver, the more rigid your design becomes, and the more you'll have to modify the driver itself whenever you want to change part of your game system. For a single person working by themselves, it really doesn't matter much. But back in the day, the typical MUD staff was a half dozen or more people. Having to schedule driver reboots and debug cases where somebody added something that made the game unstable was not a fun thing.

So it's not that I think you should NEVER modify the game driver… but I don't think it should ever have to make assumptions about how the game system works. Putting code in the driver that the mudlib calls for speed can be a good thing. Putting code in the driver that has to be changed when you want to change any other part of the game system is a pain. ;)
29 Mar, 2014, Idealiad wrote in the 33rd comment:
Votes: 0
I'm going to pull out some questions to decide from the last few replies so they don't get buried:

1. are players and NPCs 'the same'? (I think the consensus basically is yes)
2. are items and rooms the same? (this is still an open question)
3. are exits things or room properties?
4. how do client connections communicate through the driver with the game?
5. how do we handle prototypes/templates/vnums for game things?
6. how do we handle thing reloading, especially with respect to current game instances?
7. what is the general saving strategy?
8. what is the copyover/'hot reboot' strategy?

These issues are at different scales (i.e. are exits things vs. what is the saving strategy), so I think it'll be helpful to focus on the higher-level issues right now.
29 Mar, 2014, Idealiad wrote in the 34th comment:
Votes: 0
This is about 5-8 as they're related larger scale questions:

Quote
5. how do we handle prototypes/templates/vnums for game things?
6. how do we handle thing reloading, especially with respect to current game instances?
7. what is the general saving strategy?
8. what is the copyover/'hot reboot' strategy?


Some assumptions:

a. we divide content by DIKU-like areas.
b. creators should be able to use content (like items) across areas.
c. creators should be able to update the game while keeping players connected.
d. whatever a dev uses for saving data (files, SQL, etc.) the public interface to that should be the same.
e. if we update a prototype (for example, 'orc' had 10 hitpoints but now it has 15), then there should be some mechanism to update all the 'orc' instances in the live game (we might want to think about separate development and production game instances though).

(a) and (b) suppose a dependency scheme of some kind.

Does anyone think user-facing vnums/dbrefs are a good idea? I'm more in favor of using paths like DeadlyDungeon/mobs/cave_spider. Then if someone wants to use that in BlackForest they could import it. There could always be a game ID for each instance that someone could refer to if they wanted, but it's not necessary.
29 Mar, 2014, Nathan wrote in the 35th comment:
Votes: 0
Are you suggesting hard or soft areas? By which I mean would something like a room template be restricted use within a specific an area or are areas only concerned with real instances of rooms? Also, at which level of granularity do areas fall in terms of game-world divisions: land masses, regions, geographical formations (mountains, rivers, caves, forests, etc), cities, buildings, rooms.

* I'm assuming a room based system because it's easy to think about.

I like vnums/dbrefs personally, but there's no reason a naming scheme shouldn't exist given that the larger the game gets the more difficult remembering numbers is. It'd be nice to have them, but they need not be the default. For example, when building rooms/areas/etc, if I want to connect an exit or something to another room and I know that other room has id: 350 I should be able to use that instead of DeadlyDungeon\rooms\dank_cave2. As I said though, naming should come by default since otherwise you may end up with some kind of name referencing system to cover cases where the user doesn't want to remember that mob 'Ice_Dragon1' is actually id: 30463.

© Yes, yes, and also yes. An incredibly useful feature if you need to fix something. However, what sort of update do we have in mind and is there any case in which you would reasonably have to exit the game to make a modfication?
-> Obviously some systems like LPmud have this in full, but is that really necessary? There may be trade-offs here.

With regards to mobs, I think that update works as long as when you want a super_orc that has 20 hp or some major deviation that you make a new mob template. Some way to build related mobs might make sense. I.e. orc has (?)hp and super_orc has (ORC_HP * 2)hp. That is have a way for a mob to depend on another mob for part of it's definition. That way my super_orc can always have twice as much HP as an ordinary one even if I change the ordinary orc's hp from 10 -> 15.

Hot reboot/copyover should be accomplished either by total ability to make game updates with no interruption or some way to reload the game while detaching the client/socket bits from the game itself but without disconnecting them. I think having something like a chat lobby that you can attach all the detached clients too while you reload the game would be nifty since it would allow them to communicate while the game is reloading itself. Obviously that would require an account based system if we want those clients to be identifiable as a particular RL person, although we could assign some kind of temporary user object to them that we set the nickname to be the name of the current player. Of course, that's assuming a worst case where an reload somehow takes 10-15+ minutes and we'd rather the players hung around.
29 Mar, 2014, Idealiad wrote in the 36th comment:
Votes: 0
@Nathan, I'm thinking you could use a room template from one area in another area if you wanted to. An area is just a collection of world data, so it could be a house, a city, or a mountain range. What do you mean by "concerned with real instances of rooms"?
29 Mar, 2014, Nathan wrote in the 37th comment:
Votes: 0
Sorry if that's confusing. I was just trying to find a way to describe the differing notions of each area having it's own different set of unshared room templates per areas or an area which only holds/tracks actual rooms (as opposed to templates).
30 Mar, 2014, quixadhal wrote in the 38th comment:
Votes: 0
Sorry for this very long post. I know it sounds like I'm just saying "use LPMUD", but really it's just that LPMUD has already solved a great many of the issues people writing new codebases keep coming up againt, so I'm trying to illustrate how THEY did it.

———
The LPMUD system treats everything as a filename, much like UNIX. There are no "vnums", and I personally dislike them. I spent waaaaay too much time fixing vnum errors in tinyworld.wld to want to ever touch such things again. :)

Since it seem like I may be the only one with any LPMUD background (at least, most others have approached this from a Diku perspective), let me throw up a small example of a room from the Dead Souls mudlib. This is just a gate room to a newbie mansion.

#include <lib.h>
inherit LIB_ROOM;

int maxnoob = MAX_NEWBIE_LEVEL;

int PreExit(){
object guard = present("gate guard",this_object());
if(!MAX_NEWBIE_LEVEL) maxnoob = 3;
if(((MAX_NEWBIE_LEVEL && !newbiep(this_player())) ||
this_player()->GetLevel() > maxnoob) &&
(!this_player()->GetInvis() && !creatorp(this_player()) &&
!present("testchar badge",this_player()))){
if(guard && living(guard)){
present("gate guard",this_object())->eventForce("say You're too big to slip by me now. You're not going to the mansion any more.");
return 0;
}
}
if((newbiep(this_player()) || this_player()->GetLevel() <= maxnoob)
&& guard && living(guard)){
tell_object(this_player(),"You are such a newbie that the gate guard doesn't even notice you slip by him.");
tell_room(this_object(),this_player()->GetName()+" sneaks past the gate guard.",({ this_player() }) );
}
return 1;
}

static void create() {
room::create();
SetClimate("outdoors");
SetAmbientLight(30);
SetShort("Mansion Gate");
SetLong("You are standing just north of the gate to a large, "+
"beautiful mansion, which stands to the south. The "+
"Corinthian capitals on the front pillars bespeak "+
"of the wealth and importance of the person who "+
"lives here. Lush ivy wraps around the gate and the "+
"brick wall surrounding the estate.");
SetItems( ([
"gate" : "A handsome, wrought-iron entry control point.",
({"estate", "mansion"}) : "The grounds of a mansion are to the south.",
({"capital","capitals","corinthian capital","corinthian capitals"}) :
"These are the headpieces of the pillars supporting the "
"mansion's front overhang. The beautifully detailed "
"carvings of encanthus leaves distinguish them as "
"Corinthian.",
({"overhang","front overhang"}) : "A fancy and unnecessary "
"structure in front of the mansion supported by "
"columns.",
({"pillar","pillars","column","columns"}) : "Load-bearing "
"structures supporting the mansion's front overhang.",
({"ivy","lush ivy"}) : "Vines of the ivy plant run "
"over and along the walls and gate, so thickly that "
"they nearly obscure them.",
({"wall","walls"}) : "Architectural features which prevent "
"casual entry. They are made of brick and appear old "
"and strong.",
]) );
SetExits( ([
"north" : "/domains/town/room/road2.c",
]) );
SetFlyRoom("/domains/town/virtual/sky/26,99999,1");
SetInventory(([
"/domains/town/npc/mp" : ({ 3600, 1 }),
]));
AddExit("south", "/domains/town/room/mansion_ext", (: PreExit :));
AddItem(new("/domains/town/obj/lamp"));
SetProperty("no attack", 1);
}

void init(){
::init();
}


As you can see, the code looks much like C code, with a few quirks. The guy who designed LPC did it that way because he and most of his friends knew C already. The quirks include the syntax for arrays being ({ 1, 2, 3 }) and they syntax for the mapping data type being ([ "key" : "value" ]).

The first thing you'll notice is inheritance. Like C++, LPC features multiple inheritance, so you don't hard-code things that get done the same across multiple versions. LIB_ROOM would contain basic code to handle what exits are, how descriptions are handles, etc. It, in turn, will inherit lots of of things. Like any language with inheritance, if you define a function in the parent object, and don't define it again in the child, when you call that function on an instance of the child object, it will "fall back" to the parent's version.

So, here we define a PreExit() function. Normally, that's just a no-op that doesn't do anything (or maybe it checks to see if you're there and can move). But in this case, we want a guard to prevent higher level players from farming the newbie mansion. So the code checks the player's level to see if they're over the limit. It also checks to see if they're a creator, or have a teacher's badge, or are invisible. Finally, if the guard is there AND the player doesn't have a way around him, it tells the player to bugger off and returns 0.

The movement system in this particular mudlib will call the PreExit() function on the exit a player (or NPC) tries to enter and only allow the player to move if it returns true. So, returning false here prevents the player from leaving the room through that exit.

The create() function gets called whenever an instance of an object is created. In the case of rooms, they are usually unique, but for an NPC or item, every time you clone one this would be run in that new object. Most of it is pretty straighforward. The room::create() call simply calls the parent's create function BEFORE doing anything else. Then calls to initialize descriptions, lighting, SetItems() for descriptive details, etc. In the cast of SetItems(), you'll see some items have an array as the key… so if you do "look estate" or "look mansion", it displays the same detail.

SetExits() defines the room exits. You specify the direction and the path to the room where that exit goes. In this case, north would take you to a room called road2.

SetInventory() is the way you place things in the room. It is similar to the "reset" of a DikuMUD, although when placed in the create() function, it only gets run once. To have npc's respawn, there are other ways.

——–

So anyways, the reason I presented this here is that it addreses a couple of the points made above. Instead of a "vnum", you use a path identifier which happens to correspond to the filename for that object's code. Because everything in LPC works like this, there's no confusion over what vnum 3501 means, and no issues where you might be thinking of room 3501, but end up trying to edit NPC 3501 instead.

It also illustrates the way LPMUD's handle areas. In an LPMUD, there's no hard and fast driver-imposed rules for where things live. The driver doesn't care.. it's all objects with code it has to run. The mudlib designer imposes structure so the main game areas are kept somewhere like /d or /domains. In this mudlib, /domains/town is an area akin to a DikuMUD area file. They chose to place rooms, npcs, and items in their own subdirectories.

Now, because an LPMUD allows online editing of these files through the connection, there's also a security system. The short version is, any wizard can edit things in their own home directory (such as /w/quixadhal). Typically, you work on new things there so you can test them out and nobody can get to them. If you finish an area and the staff wants it in the game, they can copy it to a proper domain (/d/fooland, for example), and set the permissions so you and perhaps other wizards can continue modifying it from there.

This also addresses the issue of reloading. In an LPMUD, there's almost never a reason to reboot. Instead, you modify what you want and then update that code. Most mudlibs have systems to allow you to update a thing and all things which depend on it, or all things which it depends on. Having a "warmboot" that doesn't kick players off is a nice feature, but one you'd seldom use once your core mudlib was functional.

Saving data… here's another example:

#include <lib.h>

inherit LIB_STORAGE;

static int maxlevel = 5;

string ReadSign(){
string ret = "Only players of level "+cardinal(maxlevel)+" and below "+
"may take from this bin with impunity.";
return ret;
}

void create() {
::create();
SetKeyName("charity bin");
SetId(({"bin"}));
SetAdjectives(({"large","charity"}));
SetShort("a large bin");
SetLong("This a very large bin for holding donations to the "
"needy. Since it is in a church, and for charity, it is "
"reasonable to guess that folks above a certain level will "
"receive a minor bonus for donating valuable things, and a "
"severe penalty for taking them. There is a sign on "
"the charity bin that you can read.");
SetItems( ([
({ "sign" }) : "A sign on the bin you can read.",
]) );
SetReads( ([
"default" : "Try 'read label on bin'",
({"sign"}) : (: ReadSign :),
]) );
SetPreventGet("It's bolted down you filthy scum.");
SetMass(5000);
SetBaseCost("silver",500);
SetMaxCarry(15000);
SetCanClose(0);
SetClosed(0);
SetPersistent(1);
RestoreObject();
}

void init(){
::init();
}

varargs int eventCalculateBonus(object ob, int take){
int value, cls, sum, bonus, duration;
object thingy, who = this_player();
if(!ob || !this_player()) return 0;
if(this_player()->GetLevel() <= maxlevel) return 0;
value = ob->GetBaseCost();
cls = (ob->GetClass() || 1);
sum = (value + (cls * 1000) || 1);
bonus = (sum/10000 || 1);
duration = bonus * 10;
if(bonus > 10) bonus = 10;
if(duration > 1000) duration = 1000;
if(take && bonus > 0) bonus = -bonus - (to_float(bonus) * 0.1);
if(bonus > 0 && take) duration = -duration - (to_float(duration) * 0.1);
if(sum >= 10000 || take){
thingy = present_bonus("charity_bonus", who);
if(thingy){
bonus += thingy->GetStats()["luck"];
duration += thingy->GetDuration();
if(bonus > 33) bonus = 33;
if(duration > 5000) duration = 5000;
}
else thingy = new(LIB_BONUS);
thingy->SetBonusName("charity_bonus");
thingy->SetStats( ([
"luck" : bonus,
]) );
thingy->SetBonusDuration(duration);
if(present(thingy, who) || thingy->eventMove(who)){
object env = environment(this_object());
object wenv = environment(who);
if(env && wenv && env == wenv){
if(!take){
write("You experience a pleasant sense of kinship "+
" with the rest of the world.");
}
else {
write("You feel cheap and petty.");
}
}
}
}
return 1;
}

int eventReceiveObject(object ob){
int ret = ::eventReceiveObject(ob);
if(ret) eventCalculateBonus(ob);
return ret;
}

int eventReleaseObject(object ob){
int ret = ::eventReleaseObject(ob);
if(ret) eventCalculateBonus(ob, 1);
return ret;
}


As you might guess, this is a charity bin. Players donate things to it and get an experience bonus for doing so. Other players can take things out of it. Without going into all the details, the bits that are pertinent are SetPersistent(1) and RestoreObject() in the create() function, and the inheriting of LIB_STORAGE.

The basic idea is… when someone puts an object into the box (by whatever means), eventReceiveObject() ends up being called on it. The inherited code for this causes the box to then save its variables to a file. Likewise, when something is removed, eventReleaseObject() is called, and again the box saves its contents. When the box is created (cloned) into whatever room it sits in, the call to RestoreObject() will load that saved data. SetPersistent() just tells the mudlib not to delete the box when it's doing idle object cleanup, since you want it to stick around and not need to be reloaded when somebody walks into the room.

As I mentioned earlier, there is no loading/saving code. Nobody had to write any parsing code for this object. The driver has routines to serialize and deserialize the variables of any LPC object, and that's what gets written to the file. What does it look like?

#/domains/town/obj/charity.c
MaxCarry 5000
Opacity 100
Keys ({})
MaxRecurseDepth 3
RecurseDepth 1
MaxDamagePoints 20000
PreventGet "It's bolted down you filthy scum."
DisableChance 50
Mass 5000
PersistentInventory ({})
PersistentInventoryEnabled 1
Modify 1
Short "a large bin"
CapName "Charity bin"
ExternalDesc "This a very large bin for holding donations to the needy. Since it is in a church, and for charity, it is reasonable to guess that folks above a certain level will receive a minor bonus for donating valuable things, and a severe penalty for taking them. There is a sign on the charity bin that you can read."
Items (["sign":"A sign on the bin you can read.",])
Properties ([])
SaveRecurse 1
Saved ({"Closed", "CanClose","CanLock", "RecurseDepth", "MaxRecurseDepth", "Properties", "Locked", "Keys", "LockStrength", "Properties", "Persist", "Properties", "Class", "Worn", "Poison", "Wielded", "Value", "Cost", "Mass", "Broken", "Deterioration", "Properties",})
Cost 50000
VendorType 2
Class 1
DamageType 2
MaxClass 1
ArmorType 32768
Hands 1
WeaponType "blunt"
Money ([])
QuestId ""


Note that many of the variables directly mirror the calls we see in the create() function. Others, are inherited, or set at runtime.

———

Finally, the issue of prototypes. Falling back to the LPMUD system again, in an LPMUD, every object is one of three things. It may be an inheritable, which just means it doesn't exist as an object anywhere by itself. It may be a daemon or singleton object, meaning only one instance exists. Or it may be a clone, meaning many copies of it exist.

When you edit the code of an NPC or an item (or a room, for that matter), and you type 'update /d/foo/thing.c', the game compiles and loads that object. You can consider that to be the "prototype". In the case of a room, you only want the prototype, so you just go there (set this_player()'s environment to be that object). In the case of an orc, or a sword, you probably want a new one… so you type 'clone /d/foo/thing', and it makes a new instance of it for you. These clones are referenced by pathname, just like anything else, but each clone has a number.

So, maybe you cloned several orcs, and the particular orc you're looking at is /d/fooland/npc/orc.c#13. He may be standing in /d/fooland/rooms/forest.c, and wielding /std/weapon/club.c#93.

Does that make sense?

Edit: Edited by Davion for site integrity
30 Mar, 2014, quixadhal wrote in the 39th comment:
Votes: 0
TL;DR version….

Idealiad said:
I'm going to pull out some questions to decide from the last few replies so they don't get buried:
1. are players and NPCs 'the same'? (I think the consensus basically is yes)

I think so.
Quote
2. are items and rooms the same? (this is still an open question)

In essence, but you may wish them to be treated differently.
Quote
3. are exits things or room properties?

Properties. Doors may be things, but exits themselves are connections.
Quote
4. how do client connections communicate through the driver with the game?

What the client sends has to end up at one of many parsers. Some parsers will be a command or verb system. Others might be menu selections, or text entry. IMO, these should be softcode parsers outside the driver, because it makes it easier to extend and add new systems later. YMMV.
Quote
5. how do we handle prototypes/templates/vnums for game things?

My suggestion mimics the LPMUD system. I would prefer a hierarchy like a pathname, which allows you to be descriptive and not obfuscate what things are.
/d/fooland/room/forest.c means much more to me than vnum 13871.
Quote
6. how do we handle thing reloading, especially with respect to current game instances?

That depends on the granularity of the reload, and the reason behind it.
If the system is well designed, there shouldn't be a need to do a full reboot very often.
Quote
7. what is the general saving strategy?

That depends on driver vs. softcode. If things are in softcode, the driver should provide a means to serialize and deserialize any object's data, and that should be read from or written to a file or database entry. If things are in the driver, each thing will need to have some loading/saving code, and perhaps multiple file formats defined.
Quote
8. what is the copyover/'hot reboot' strategy?

That depends on the level of persistence, and a fully persistent game is a whole different idea from a reset based one. If softcode is heavily used, copyover is not really important. If more things live in the driver, it becomes critical.
30 Mar, 2014, Idealiad wrote in the 40th comment:
Votes: 0
Good comments quix, but please throw some newlines into that ExternalDesc, or I need to buy a new monitor :D.
20.0/64