RanvierMUD / core

Core engine code for Ranvier
https://ranviermud.com
MIT License
45 stars 41 forks source link

Area Instancing #97

Open shawncplus opened 5 years ago

shawncplus commented 5 years ago

There are currently a few architectural road blocks preventing multiple instances of a single area.

There are other questions but I think the answers to these are in the responsibility of bundle developers:

seanohue commented 5 years ago

This is kind of opinionated but I think that logging back into a destroyed instance should emit an event so the developer can decide what to do. An example implementation might involve recreating the instance, then dropping the player back in to a starting room.

nelsonsbrian commented 5 years ago

What I wanted to make for it was the ability to pass in a config object that you can utilize for npcs and objects. I would have to make my own custom helpers, but if you did area.clone(config) and had something like config = { health: 1.4, ...} and have that that health percent be applied to all the npcs being loaded.

Essentially be able to scale the attributes on items or npcs being loaded in the zone.

nelsonsbrian commented 5 years ago
ratacat commented 5 years ago

How are area instances indexed?

I'm not understanding what you mean...like added to RoomManager / AreaManager type of thing?

What happens to players who log out in an instance and log back in after the instance has been destroyed?

I agree with Sean, just emit an event.

Items from instanced/non-instanced versions of an area should save/load correctly

I suspect that we should also approach mobs in a similarly careful way. It wouldn't be out of the ordinary for games to incorporate features that might reference a mob. Such as taming, or npc memory. We'd want any references to mobs from previously existing instances to be handled well.

For general modes of operation, I feel like there are two distinct ways games tend to use instances.

  1. As a load balancer for mobs, quests, resources, overcrowding etc...
  2. As an isolation mechanic for individual or group quests, raids...etc.

I wonder how this would interact with quests? They have an area in their id.

Here's an odd thought, when cloning an area as a new instance, perhaps you leave the area part of the entityReference the same, and add an instance id onto each of the id's for mobs, rooms, items...I don't know, maybe that would be terrible implications, maybe not, haven't looked into it versus changing the area part of the entityReference. Or maybe there's an elegant way to go underneath all of that anyway, I'm not sure.

Essentially be able to scale the attributes on items or npcs being loaded in the zone.

I LOVE THIS IDEA BRIAN!!! SO GOOD!

Can instanced zones be saved for next reboot?

Well considering Ranvier engine at the moment doesn't serialize rooms, probably not.

seanohue commented 5 years ago

I don't see why there couldn't be event hooks in place that, with a custom DataSource, would enable saving instanced zones. But you are correct that it does not serialize rooms by default.

shawncplus commented 5 years ago

Basic instancing functionality is now testable on the instancing branch of core. You'll also need to update your example bundles to the most recent code. For bundle-example-player-events you'll need to be on the instancing branch as well.

Overview

title: Test Area
instanced: player

Non-falsey values of instanced are arbitrary and used purely for bundle developers to change how a given area should be instanced.

Areas which have a non-falsey value for instanced will not be created/hydrated at server startup. Instead, creation of these areas is left entirely to bundle developers.

Creating an instance

An example implementation can be seen here https://github.com/RanvierMUD/bundle-example-player-events/tree/instancing

The gist being:

Removing an instance

Like creating an instance, how an instance gets removed and cleaned up is entirely up to the bundle developer. The removeArea(area, instanceId) will dereference the area and allow for creation with the same key but it's the responsibility of the caller of removeArea() to do things like halt combat, make sure players don't get stuck in a dereferenced area, cancel any effects, etc.

shawncplus commented 5 years ago

As to @nelsonsbrian 's idea I've added events for an area when an NPC is added/removed. This currently happens when an NPC is spawned by room.spawnNpc.

To accomplish something like that you would create 2 effects. The first being an Area effect which listens for npcAdded and based on its configuration would create an NPC effect and attach it to the new NPC.

It would look something like:

Somewhere a configuration exists that says { health: 1.4, ..., an instanced area is generated, you initialize the new area-bonuses effect passing in that config, then attach it to the area.

Whenever an NPC gets spawned in that area your effect will be listening for it and when it does will be able to create a new effect like area-npc-bonus, and attach it to said NPC.

ratacat commented 4 years ago

I've found some an unexpected thing come up with using the instancing branch. Largely it works awesome! Basically, if you try and use state.RoomManager#getRoom to fetch a room from an instance that isn't loaded, it can't find it. Maybe this makes since as the rooms don't technically exist yet. I've run into it using the teleport command, as well as the starting room.

I've been using this function, which comes from someplace else. Just figured I'd share in case anyone else runs into this.

RoomUtil.getRoom = function(state, player, nextRoomId) {
    const nextAreaName = state.AreaFactory.getNameByEntityReference(nextRoomId)
    let nextRoom = null
    if (state.AreaFactory.isInstanced(nextAreaName)) {
        const instanceId = player.name
        if (player.area != nextAreaName ){
            B.sayAt(player, `W]Entering instanced area: w]${nextAreaName}`)

            if (!state.AreaManager.getArea(nextAreaName, instanceId)) {
                B.sayAt(player, "W]No instance exists, creating...")
                state.AreaFactory.createInstance(state, nextAreaName, instanceId)
            }
            B.prompt(player)
        }

        nextRoom = state.RoomManager.getRoom(nextRoomId, instanceId)
    } else {
        nextRoom = state.RoomManager.getRoom(nextRoomId)
    }

    return nextRoom
}