Recent Posts

ExVenture Updates for November 2017

Posted on 28 Nov 2017

The last month for ExVenture has seen a lot of NPC changes and lots of documentation. The documentation website is exventure.org. You can see the latest additions here on MidMUD, my running instance of ExVenture.

NPC Events

I have expanded a lot on the event system for NPCs. You can see a full list at exventure.org.

NPCs wandering around, and triggering aggro

The most exciting thing that happened was NPCs can now fight back! They will also wander around near where they spawned and aggro players if they stumble into any. This is all stored and edited in a huge JSON array right now, but I would like to eventually make it a nice interface. This is the bandit's events on MidMUD:

[
  {
    "type": "room/entered",
    "action": {
      "type": "target"
    }
  },
  {
    "type": "combat/tick",
    "action": {
      "weight": 10,
      "type": "target/effects",
      "text": "{user} slashes at you.",
      "effects": [
        {
          "type": "slashing",
          "kind": "damage",
          "amount": 2
        }
      ],
      "delay": 2.0
    }
  },
  {
    "type": "tick",
    "action": {
      "type": "move",
      "max_distance": 2,
      "chance": 25
    }
  }
]

Documentation

I have finished a first pass at the documentation site. All of the large systems in the admin panel have documentation at this point. There are still a few small things with rooms and NPCs that need documentation.

Item instances

Items now create instances, and are tracked as such. This allows extra data to hang out with the item id. Right now only created_at is included with the id.

Minor Tweaks

A few minor tweaks that make life nicer:

  • Registration can be via the web
  • Registration will display errors if any come up
  • Commands parse a little better, backend code could be deduplicated
  • Common mistakes such as kill target and point users towards the help system
  • Set a target when using a skill, magic missile target
  • Ask for optional email, and allow registering via the home page

Next Month

Next month I'd really like to continue with combat. Right now there is a static amount of damage you will do, it might be nice to do a range of damage. I also want to add in some kind of dodge mechanic. Items also don't affect combat at all either.

ExVenture Update November 2017

Posted on 01 Nov 2017

The last month for ExVenture has seen mostly metrics improvements and some client additions. There is also a new website for it at exventure.org. You can see the latest additions here on MidMUD, my running instance of ExVenture.

Metrics

Grafana Dashboard

I started looking into Prometheus for work and started adding in metrics for various aspects of ExVenture. Some of the metrics include:

  • exventure_command_parsed_in_microseconds
  • exventure_command_ran_in_microseconds
  • exventure_command_bad_parse_total
  • exventure_player_count
  • exventure_session_total
  • exventure_login_total
  • exventure_login_failure_total
  • exventure_new_character_total

GMCP

The server also supports GMCP now, either through normal telnet or the web client. The web client has it pushed over as a special websocket event. GMCP Modules now supported are:

  • Character
  • Character.Vitals
  • Room.Info
  • Room.Characters.Enter
  • Room.Characters.Leave
  • Target.Character
  • Target.Clear
  • Zone.Map

More information about these can be found on the wiki.

Client

New Client

The updated web client uses the new GMCP pushes to allow stat bars and a side bar with room information and a mini map.

Maps

Maps got a fairly big update in that multiple layers are supported, there are doors now, and you can go up and down. Multiple layers allow things like going under a bridge or heading up to the second floor of a house.

Doors are now in place and prevent movement between rooms if they are closed, obviously. They are indicated with a = for closed and / for open states.

Finally up and down are movement directions. There isn't map indication yet, but I want to add something similar to this:

+---+
|[ >|
|   |
|< ]|
+---+

Next Month

I want to add more in terms of NPC interaction. I started on an event system for NPCs that can really only respond to things it hears and when a character enters a room. I want to add in targetting and combat back from NPCs.

A few people have signed into the game and I get to see what they attempt to do and failed at. I think a goal every month will be to continue to smooth out things that people are attempting to do.

Links from ElixirConf 2017

Posted on 09 Sep 2017

These are the links I gathered while watching talks at ElixirConf 2017.

Adding Phoenix to an OTP Elixir App

Posted on 05 Sep 2017

For my side project, ex_venture, I wanted to add a web client that allowed players to connect not just via normal telnet. This meant I needed to add Phoenix. I was excited to try this out because the phoenix devs keep saying how you should just think of it as a layer for your app, not the app itself. Since I had an app already this was the perfect trial.

Brief note: ex_venture is a MUD engine, the protocol up until now was only telnet.

Adding Phoenix

Adding phoenix was a pretty simple affair. It took about 2 hours to get it up and running in a simple form. Here is the commit that adds it entirely. I mostly copied from a new phoenix project and took what I wanted.

The fun part was the TelnetChannel that talked with my session module similar to the normal telnet socket.

Phoenix <-> OTP communication

Another interesting part of this addition was I could add a web admin panel to the game. With this I wanted live updates to the game. If I added a new room to a zone, then I should see it reflected immediately in the game. I achieved this by creating bounded contexts that talk to the data layer then push the update into the OTP layer.

Samples

Updating a zone

This shows off updating a zone and pushing the change live into the game. The controller knows nothing about the OTP. Web.Zone is a layer between Phoenix and the game.

Web.Admin.ZoneController
alias Web.Zone

def update(conn, %{"id" => id, "zone" => params}) do
  case Zone.update(id, params) do
    {:ok, zone} -> conn |> redirect(to: zone_path(conn, :show, zone.id))
    {:error, changeset} ->
      zone = Zone.get(id)
      conn |> render("edit.html", zone: zone, changeset: changeset)
  end
end
Web.Zone
alias Data.Zone

def update(id, params) do
  zone = id |> get()
  changeset = zone |> Zone.changeset(params)
  case changeset |> Repo.update do
    {:ok, zone} ->
      Game.Zone.update(zone.id, zone)
      {:ok, zone}
    anything -> anything
  end
end
Game.Zone
# pid expands to the elixir Registry
def update(id, zone) do
  GenServer.cast(pid(id), {:update, zone})
end

def handle_cast({:update, zone}, state) do
  {:noreply, Map.put(state, :zone, zone)}
end

Adding a new room to a zone

This shows off how the Room admin will spawn a new room in a zone after being created. The controller knows nothing about OTP. Web.Room is a layer between Phoenix and the game.

Web.Admin.RoomController
alias Web.Room

def create(conn, %{"zone_id" => zone_id, "room" => params}) do
  zone = Zone.get(zone_id)
  case Room.create(zone, params) do
    {:ok, room} -> conn |> redirect(to: room_path(conn, :show, room.id))
    {:error, changeset} -> conn |> render("new.html", zone: zone, changeset: changeset)
  end
end
Web.Room
alias Data.Room

def create(zone, params) do
  changeset = zone |> Ecto.build_assoc(:rooms) |> Room.changeset(params)
  case changeset |> Repo.insert() do
    {:ok, room} ->
      Game.Zone.spawn_room(zone.id, room)
      {:ok, room}
    anything -> anything
  end
end
Game.Zone

When the Room.Supervisor comes online it lets the zone know it's PID to spawn new rooms in.

def spawn_room(id, room) do
  GenServer.cast(pid(id), {:spawn_room, room})
end

def handle_cast({:spawn_room, room}, state = %{room_supervisor_pid: room_supervisor_pid}) do
  Room.Supervisor.start_child(room_supervisor_pid, room)
  {:noreply, state}
end
Game.Room.Supervisor

The Room.Supervisor starts the new room in the supervision tree.

def start_child(pid, room) do
  child_spec = worker(Room, [room], id: room.id, restart: :permanent)
  Supervisor.start_child(pid, child_spec)
end

Conclusion

Adding Phoenix to an regular OTP app was incredibly simple and the Phoenix team did what they set out to. I hope you explore the rest of the app to find more examples of Phoenix <-> OTP communication.

Using GenServer :via

Posted on 11 Aug 2017

For a side project I wanted to figure out how to use :via with GenServer. I have two different GenServers that should respond to similar GenServer interface, NPCs and User Sessions.

I went this route because I already had two registries going, one for each type of instance. The NPCs registry was a standard unique registry and the session registry was a duplicate registry so I could easily determine which sessions were online and had a connected user.

With that in place it was very easy to get a :via module set up. I did not have to handle two of the required functions for :via which deal with registering and unregistering.

:via whereis_name

The first thing I had to get going was finding the correct PID given an existing registry. For NPCs I simply delegated off to the elixir registry since it was already built in. I patterned matched against a tuple to determine the split.

def whereis_name({:npc, id}) do
  Registry.whereis_name({Game.NPC.Registry, id})
end
def whereis_name({:user, id}) do
  player = Session.Registry.connected_players
  |> Enum.find(&(elem(&1, 1).id == id))

  case player do
    {pid, _} -> pid
    _ -> :undefined
  end
end

:via send

This was a similar instance, delegate off to the standard registry for NPCs and find the pid for user sessions.

def send({:npc, id}, message) do
  Registry.send({Game.NPC.Registry, id}, message)
end
def send({:user, id}, message) do
  case whereis_name({:user, id}) do
    :undefined ->
      {:badarg, { {:user, id}, message} }
    pid ->
      Kernel.send(pid, message)
      pid
  end
end

Conclusion

It was fairly easy to get this going and now I can send a message to either set of GenServers by only know their database ids. I can start to hide the knowledge of if the server is a session or an NPC pid going forward for things that won't care.

Eric Oestrich
I am:
All posts
Creative Commons License
This site's content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License unless otherwise specified. Code on this site is licensed under the MIT License unless otherwise specified.