Adventures in Symfony

Using Open Source Software to Make an MMO Game

Margaret Staples

Adventures in Symfony

Using Open Source Software to Make an MMO Game

Margaret Staples

Hello, nice to meet you!

I'm Margaret Staples

Hello, nice to meet you!

I'm Margaret Staples

I've been developing a Social Strategy City-Builder RPG

game of awesomeness.

Overview

Planning

Core Systems Development

Player Testing

Feature Completion

Deployment

Planning

Planning

Inventory Design Info

Inventory Design Info

Locate, Categorize, Analyze.

Planning

Conceptualize Development

Conceptualize Development

“No Battle Plan Survives Contact With the Enemy”

Planning

Initial Database Design

Initial Database Design

Resources

Goods

Buildings

Items

Actions

Districts

Classes

Attributes

Units

Planning

Learn the Framework

"Learn" the "Framework"

Core Systems

harvesting, processing, crafting, training, building, actions, leveling

Core Systems

Initial Database Content

Initial Database Content

Install Doctrine Fixtures Bundle

Create Entities

> composer require doctrine/doctrine-fixtures-bundle
 // src/Group/NameBundle/Entity/Resources.php
/**
 * @ORM\Entity
 * @ORM\Table(name="resources")
 */
> php app/console doctrine:schema:update --force
> php app/console doctrine:generate:entities Group/NameBundle/Entity/Resources

Core Systems

Initial Database Content

Initial Database Content

Fixtures and Generated Data

Google Docs

Design Documents

Fixture Spreadsheets

Export to CSV

// [ProjectDirectory]/src/[Group]/[Bundle]/Datafixtures/ORM/exports

Core Systems

Initial Database Content

Initial Database Content

// src/Group/NameBundle/DataFixtures/ORM/LoadResourcesData.php

namespace Group\NameBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Group\NameBundle\Entity\Resources;

class LoadResourcesData implements FixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $filename = __DIR__.'/exports/resources.txt';
        $fd = fopen($filename, "r");
        $contents = fread($fd, 10000);
        fclose($fd);
        $splitcontents = explode(',', $contents);
        $counter=0;
        foreach ($splitcontents as $each) {
            if ($counter == 0) {
                $resource = new Resources();
                $resource->setName($each);
            }
            ...
        }
    }
}
> php app/console doctrine:fixtures:load

Core Systems

Initial Database Content

Initial Database Content

// src/Group/NameBundle/Command/startOneCommand.php

namespace Group\NameBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Output\OutputInterface;
use ONN\BrunBundle\Entity\Resources;

class startOneCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this->setName('start:one')
             ->setDescription('setup the game server');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $em = $this->getContainer()->get('doctrine')->getManager('default');

        $output->writeln('step one process: generating Wilderness');
        $this->generateWilderness();
        $output->writeln('... complete');
    }
}
> php app/console start:one

Core Systems

Expanded Interactions

Fundamentals

Login, Character Creation, Settlement Creation

Player (FOS UserBundle)

Lord Character

Hero Character

Settlement

*Facebook, Google+, Twitter, Steam

Core Systems

Initial Interactions

Initial Interactions

Harvest, Process, Construction

Core Systems

Initial Time Based Events

Initial Time Based Events

Harvest, Process, Construction

Timed Event Tables:

  • EventMin
  • EventHour
  • EventDay
  • EventWeek

Core Systems

Initial Time Based Events

Initial Time Based Events

Harvest, Process, Construction

// src/Group/NameBundle/Command/timeCommand.php
...
class timeCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
            ->setName('time')
            ->setDescription('Triggers timed event processing')
            ->setDefinition(array(
                new InputArgument(
                    'timer',
                    InputArgument::REQUIRED,
                    'timer name'
                ),
            ));
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $timer = $input->getArgument('timer');
    }
}
> php app/console time days

Core Systems

Initial Time Based Events

Initial Time Based Events

Harvest, Process, Construction

#!/bin/bash
# scripts/brunday.bsh

logfile=~/logs/bruncycle.log
exec >> $logfile 2>&1
export PATH
echo "Brun Daily Cycle:" >> ~/logs/bruncycle.log
php ~/www/app/console time days 
> crontab -e

0    0    1    *    *    ~/scripts/brunback.bsh
01   *    *    *    *    ~/scripts/brunday.bsh
01   01   *    *    *    ~/scripts/brunweek.bsh
*/15 *    *    *    *    ~/scripts/brunhour.bsh
*/5  *    *    *    *    ~/scripts/checkcycle.bsh

Core Systems

Initial UI

Initial UI

Like a MUD with web forms


public function someFormAction(Request $request, $active = 'no') 
{
    $form_name = "someForm";
    $form_path = "some_form";
    
    $form = $this->createFormBuilder(null)
        ...
        ->getForm();

    return $this->render('GroupNameBundle:None:formFragment.html.twig', [
        'form' => $form->createView(), 
        'form_name'=>$form_name,
        'form_path'=>$form_path
    ]);
}

Core Systems

Initial UI

Initial UI

Like a MUD with web forms

<script>
    function formSubmit(path,form_name) {
        $.post( path,
            $('#form_'+form_name).serialize(),
            function(data){
                $('#container_'+form_name).empty().append(data);
            }
        );
        return false;
    }
</script>

<div id="container_{{ form_name }}">
    <form id='form_{{ form_name }}' method="post" 
        {{ form_enctype(form) }} name="{{ form_name }}" >
        
        {{ form_widget(form) }}
        
        <input id="{{ form_name }}" 
           onclick="formSubmit('{{ path( form_path, { 'active': 'yes' }) }}','{{ form_name }}')" 
           value='Submit' class='btn btn-small btn-custom' />
    </form>
</div>

Core Systems

First Exposure to "Community"

First Exposure to "Community"

Core Systems

Expanded Interactions

Expanded Interactions

Forums

Crafting

Training

Actions

Leveling

Ranking

Local Markets

Global Market

Game Market

Fealty

Equipping

Friendship

Enmity

Marriage

Handfasting

Divorce

Dueling

Diplomacy

Scenarios

Armies 

Player Testing

dissolution of assumptions

Player Testing

Players Do Things Differently

Players . . .

Do Things Differently

Create Unpredicted Strain

Provide Design Clarity

Player Testing

Communicating with Players

Communicating with Players

Bug Report Form

Game Forum Posts

Game Forum PMs

Other Forum Posts

Emails

Tweets

Skype

Google+ Chat

Steam Chat

Player Testing

Prioritizing Reports and Requests

Prioritizing Reports and Requests

Feature Completion

Feature Completion

Organization Concerns

Organization Concerns

What Goes Where, With Access to What

Controllers

Services

Repositories

Commands

Feature Completion

Peripheral Features

"Peripheral" Features

Require as Much or More as "Core" Features

JIT Design Completion:

Less Flexible, Harder Refactors

Feature Completion

Deliberate Testing

Deliberate Testing

Feature Completion

Time Based Events: Redux

Time Based Events: Redux

RabbitMQ

> composer require oldsound/rabbitmq:1.*
# app/config/config.yml
old_sound_rabbit_mq:
    connections:
        default:
            host:     'localhost'
            port:     5672
            user:     %rabbit_user%
            password: %rabbit_password%
            ...
    producers:
        queue_task:
            ...
    consumers:
        queue_task:
            ...

Feature Completion

Time Based Events: Redux

Time Based Events: Redux

RabbitMQ

# src/Group/NameBundle/Consumer/ProcessQueueConsumer.php
...
class ProcessQueueConsumer implements ConsumerInterface
{
    public function __construct(EntityManager $entityManager, $kernel)
    {
        ...
    }

    public function execute(AMQPMessage $msg)
    {
        $kernel = $this->kernel;
        $command = $msg->body;
        ...
        $path = str_replace('scripts','','php '. $kernel->getRootDir() . '/console ' . $command);
        $process = new Process($path);
        $process->start();
        ...
        return $process->getOutput();
    }
}
$command = 'update-act-access '.$char_id;
$this->get('old_sound_rabbit_mq.queue_task_producer')->publish($command);

Feature Completion

Time Based Events: Redux

Time Based Events: Redux

#!/bin/bash
# scripts/checkworkers.bsh

NB_TASKS=15
SYMFONY_ENV="prod"

TEXT[0]="app/console rabbitmq:consumer -l 256 queue_task"

for text in "${TEXT[@]}"
do

NB_LAUNCHED=$(ps ax | grep "$text" | grep -v grep | wc -l)

TASK="/usr/bin/env php ${text} --env=${SYMFONY_ENV}"

for (( i=${NB_LAUNCHED}; i<${NB_TASKS}; i++ ))
do
  echo "$(date +%c) - Launching a new consumer"
  nohup $TASK > consumer_$i.log &
done

done

RabbitMQ

Feature Completion

Reflecting Change for Active Players

Reflecting Change to Active Players

Feature Completion

API

API

Feature Completion

3rd Party Code Upgrades

3rd Party Code

Doctrine Fixtures

FOS User

FOS Facebook

FOS Twitter

Bitgandtter Google

Widop PHPBB

Oldsound RabbitMQ

Cboden Ratchet

Egeloen CKeditor

Avalanche123 Imagine

Bundles

APIs

Steam

Twitter

Google

Facebook

Integrated Intsall

PHPBB

MediaWiki

SVG Avatars Generator

Deployment

Deployment

Estimates are Hard

Estimates are Hard

Deployment

Magic and Dreams

Magic and Dreams

Payments

Players

Settlement Servers

Interest

Decay

Adventures in Symfony

Using Open Source Software to Make an MMO Game

brunegame.com

Questions?

brunegame.com

Adventures in Symfony

Using Open Source Software to Make an MMO Game

brunegame.com

Margaret Staples

twitter: @dead_lugosi

freenode: deadlugosi

email: mstaples@aesopgames.com

Made with Slides.com