• Welcome to Final Fantasy Hacktics. Please login or sign up.

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Topics - Kivutar

Tethical / Artificial Intelligence
July 29, 2013, 07:22:53 am
Hi everybody.

I've not followed Tethical progress for a while. Is there any progress on artificial intelligence?

If not, I've created a starting point for people who wants to try coding an AI for Tethical.

The code is on my github, in the AI branch here : https://github.com/Kivutar/tethical/tree/AI

I know that this repo is not up to date, but I don't have access to the new one, so I put the code here and it can be ported.

What does this code do for now?

I added an 'ai' flag to characters so they can be controlled by the server instead of asking the clients. For now, I put the flag on every female character, so if you want to try the AI, you can just log in with two clients and create two teams of female only character, and watch the AI at work.

The AI is in the AI.py file server side. For now it does almost nothing : characters just move around at random places on the map without fighting.
However, I talked to fdc on IRC and he knows very well how AI works in FFT and want to try coding it's own AI based on how chess AI are done.

Here is a video showing the AI moving around :

Tethical / Map exporter
May 24, 2012, 05:21:01 am
The blender to egg exporter (called Chicken) we were using is no more supported by recent versions of blender. There is a new tool called YABEE, but exporting our maps to egg with it give me an error.
Tethical / Sounds
May 09, 2012, 08:41:40 am
I found a tool to generate sound effects. rdein the creator of Momodora uses it for his games. http://www.drpetter.se/project_sfxr.html
We could use it to replace the corpyrighted sounds in our git repo.

This one also : http://labbed.net/software.php?id=labchirp
Tethical / Code refactoring
May 08, 2012, 08:21:29 am
This week end, I refactored the code of tethical, client and server. All have been pushed on github. Here is an overview of the new code organization :


The server was composed of a big main.py doing all the stuff, with 3 or 4 business classes containing specific algorythms like moves an loading maps. I did not touch these classes, but i exploded main.py into many parts.


main.py is now 80 lines of code. It is pretty close to the hello world server in panda3d documentation, this means it listen to incoming packets with a queued manager, and dispatch the work to controllers. main.py also stores the session.
The code I used to dispatch the work is there:
    def processData(self, datagram):
        iterator = PyDatagramIterator(datagram)
        source = datagram.getConnection()
        callback = iterator.getString()
        getattr(globals()[callback], 'execute')(self, iterator, source)

The controllers

Controllers are now stored in a new folder to separate them from the rest of the code (https://github.com/Kivutar/tethical/tree/master/server/Controllers). A controller is a python module which is named from the pyDatagram identifier. It get the data from the client, call business classes like Map or Move etc, (in a later time it will query the database, and finally send back a reply to the client(s).
Here is an exemple of controller, the GET_WALKABLES controller, triggered when a client need to know on wich tiles a character can walk:
import Move

# Return the list of walkable tile for a character to a client
def execute(server, iterator, source):
    charid = iterator.getString()
    party = server.parties[server.sessions[source]['party']]
    walkables = Move.GetWalkables( party, charid )

    server.send.WALKABLES_LIST(charid, walkables, source)

What does it do? First we import the Move business class, wich contains the needed algos. A controller is composed of a single method called execute with allways the same signature: (server, iterator, source)
- server is a referece to the server to access the sessions data.
- iterator is the pydatagram iterator to extract data sent by the client, in this case the client sent the "character id"
- source is a reference to the client who sent the datagram.
The controller invoques a method from the Move module.
Finaly, it sends back a datagram (here WALKABLES_LIST) to the client.


Send.py is a class where I put all the datagrams the server will send to the client. It factorizes the code and hde the datagram complexity. So we can write
server.send.WALKABLES_LIST(charid, walkables, source)
instead of
myPyDatagram = PyDatagram()
self.cWriter.send(myPyDatagram, player)


Now the big part. As for the server, my goal was to explode main.py. So,
- I created a Send.py for the client, similar to what the server now have
- Controllers have been moved out of the main.py, following exactly the organization of the server
I won't waste time describing these changes, please refer to the server side.

Important thing to know: DirectObjects. DirectObjects are a class provided by panda3d. DirectObjects can listen to keyboard keys and bind them to functions, and we can also ask them to stop listening to the keys. In Tethical GUI module, a lot of my classes inherit from DirectObject so they can define what keys they want to hear, and when I don't need them no more, I destroy them and they don't bug me with their binding. Sometimes I also ask them to stop listening, this can be usefull when you have instanciated more than one DirectObject of different kind at the same time. And this is what happens now in the client.


Draws the sky during a battle. Will later contain sequences to change the color, for weather effetcs.


Draws the cursor, move it to a tile, change it's color. Will later contain tasks to animate the pointer with cosinus.

Display, move and hide the Active Time flag on sprites.

Contains functions that returns sequences (animation) usefull during a battle, like battle introduction, attacking, walking, etc...

A very important class to manage the tile matrix during a battle. It can generate a matrix of tile from map data. Draw colored zones by changing the color of these tiles. Find character references stored in these tiles.


KeyboardTileTraverser is a DirectObject managing the moves of the cursor on the tile matrix. It listens to arrow keys, and round and cross button. This KeyboardTileTraverser also contains methods that are only usefull for tile traversing with a keyboard, you wouldn't need to listen to arrow keys if you where using a mouse to select tiles. This means that this class could be replaced if you want to port Tethical to the wii or other pointer or touch devices.
The client will ask the KeyboardTileTraverser to listen to keys in these cases:
- selecting a tile to move a character
- selecting a tile to attack a character
- free mode, when canceling the battle menu
The client will ask the KeyboardTileTraverser to stop listening in these cases:
- displaying dialogs
- displaying a DirectionChooser
- in passive mode (when the other team is active)


CameraHandler is also a DirectObject to manager the camera. It works very similar to KeyboardTileTraverser and is keyboard driven so we may rename it KeyboardCameraHandler. It is enabled and disabled in almost the same phases as the KeyboardTileTraverser, except when displaying a DirectionChooser, the camera remains active.


In this class, I started to move battle specific functions. It is not complete yet. I will maybe move all that is battle related out of the main.py in this class and rename it Battle or BattleMechanics.


This rework of the code took me 4 days. This is pretty short, and I think due to the very small size of the codebase. Python did not come in my way, so it was a good choice for writting a prototype as we need flexibility.
The actual organization of the code is still far from perfect, but some bugs have been fixed, the code have been cleaned and symbols renamed more consistantly and a lot of things are now commented.
Please read the new code and tell me what you like/dislike.
This is a tutorial about font making for Tethical.

For this, we'll take as exemple the fonts used to display the cursor's height during the battle (called fftcoords):

1) Screenshots

The first step is to take a lot of screenshot from FFT. Use an emulator with no aliasing, with a 320x240 or 256x240 resolution.

2) Analyse!

Put all the characters in a single bitmap file, and start to analyse them.

What is the best height to use? Align the characters as you want to display them in game, and look at the top and bottom pixels.
Our fftcoords uses a 32 px height (don't have to be power of two).

What width to use for each character?
This an vary a lot. You must also take care of this in the 3rd question:

How much pixels are used to space the characters? This setting can change for each character! You can also use negative margins to fix some special cases
Our fftcoords was a real nightmare for spacing. It uses negative margins, but the emulation is still not complete. However, normal people won't be able to notice it, so no problem.

Which alignment do you need for the words that will result from the combination of the characters, left, right, both?
fftcoords will be used to display right aligned words of the form "12.5h" only. The "h" must not move. This may lead you to use left margins instead of right margins.

What characters are available in this font? What special characters? How will you handle it in your code?
This font have a special character: there is two "5", and one is smaller than the others characters.
There I had to choose beetween ["1","2","3","4","5","6","7","8","9",".5","h"] and ["1","2","3","4","5","6","7","8","9",".","h"] where ".5" is a special character used instead of "a".
I choose the first solution because it was easier to code. I just have to write "12ah" to display "12.5h" with the small "5".

3) Fontforge

The software I use to modelize the font is called Fontforge and is free and opensource. It uses SFD as internal format, and you can find some .sfd in the font folder of Tethical's client to get started.
Yes Fontforge is ugly, but very complete.

So let's open our FFTCoordinates.sfd and see what it look like:

The cells with a red cross are disabled cells. The others are for used characters. You can note the A is used (for the .5 special char).

Click on a cell to open the character editor. You may be surprised to discover that I did not draw the real character in Fontforge. It is because Fontforge (and almost all known font formats) does not handle more than one color for the pixel fonts. So all this Fontforge step will be only usefull to setup the height, widths, and margins of our characters.

Once in the character editor, you can type CTRL+SHIFT+L to set the "case", which means the width of the current character.
You can increment the case to create a positive margin. Let's say that all our characters will have one more pixel on the same side to be displayed spaced.
You can draw outside the case border to fake a negative margin.

4) Exporting to BDF

BDF is a pixel font format. Fontforge can export to BDF. And Panda3d egg_mkfont utility can import BDF.

In Fontforge main window, type CTRL+SHIFT+G to generate the font. A setting popup will appear. Choose BDF for the pixel format, no vectorial formats, and set 12 for the size of the font, this is arbitrary number, but remember it because we will pass it to egg_mkfont in a later step.

You don't have to change the name of the font with the *. You will obtain a FFTCoordinates-12.bdf file.

Note that you can open back this bdf with Fontforge. Almost no data is lost. You could even stop using your SFD file after this step.

5) BDF to EGG

Panda likes eggs. It also supplies an small utility called egg-mkfont to convert fonts to egg+png. If you open the already existing fftcoords.egg, you will find this comment in the first line:

egg-mkfont -noaa -sf 1 -o fftcoords.egg FFTCoordinates-12.bdf -ps 20 -ppu 12

This is the command line I used to export the font.
-noaa means no anti aliasing
-sf 1, I don't remember
-ps 20, I don't remember
-ppu 12, 12 pixel per unit, our arbitrary number

You will obtain two files, fftcoords.egg and fftcoords_1.png.

Note: since I updated my Panda on Arch Linux, I can no more pass this step. I get this error:
:pnmtext(warning): Unable to set  to 10pt at 432 dpi.

6) Fix your egg

Open you egg with a text editor, and use search and replace to make your texture use the following options:

<Texture> fftcoords104 {
 <Scalar> minfilter { nearest }
 <Scalar> magfilter { nearest }
 <Scalar> anisotropic-degree { 0 }
 <Scalar> quality-level { best }
 <Transform> {
   <Matrix3> {
     0.140625 0 0
     0 0.28125 0
     0.664062 0.460938 1

I changed the filters, and removed another thing that was locking colors.

Note: egg-mkfont may have some new options to automatise this step.

7) Fix your png

Open your PNG with an image editor. Change the mode to RGB. Add an alpha chanel. Remove all this black and white, and paste the characters from you screenshots in the right places.

8) Test your font in Panda

Using this script:

from Config import *
from direct.directbase.DirectStart import *
from pandac.PandaModules import *
import GUI

v = 1.0/120.0
scale = 2*12.0/240.0
coordsfont = loader.loadFont(GAME+'/fonts/fftcoords')


coordstn = TextNode('tn')
tnp = aspect2d.attachNewNode(coordstn)
tnp.setPos(v*112, 0, v*66)

Tethical / [MAP] Unnamed City
July 02, 2011, 08:11:43 pm
This is the map I am actualy working on.

It may take some time to release it due to my switch to Arch Linux: my version of Blender is now bleeding edge and the Chicken Exporter does not support it yet.
Tethical / [MAP] Stairs Towers
May 30, 2011, 10:56:47 am
I'm trying to modelize Cheetah's idea. A single tower looks ok, but with many towers the result is insane.

Maybe with 2 towers this map would be good for a capture the flag.
I'm looking for these images.
The one see see attached have been screen scraped by me, and been edited with Gimp but the result is not neat.
Is there a way to extract the native images from the ISO?

Tethical / [Request] Title screen background
May 25, 2011, 05:18:25 pm
I'm looking for the background of the title screen (without the text).

Thanks :)
Tethical / [Request] Cursor textures
May 25, 2011, 05:05:22 pm
Hi all, I need help to get the textures of the battle cursor.
I need the frames of the blue tile, and the cross over the head.
Can one of you get it for me?

How do you get these textures? Directly from the ISO or using an emulator?

Tethical / How to create a custom map
May 12, 2011, 10:17:56 am
This guide demonstrate how to create a custom map for tethical from scratch. Step by step.

Step 1: Modeling a flat map using Blender

  • Download, install Blender and launch it.

  • You will see a cube.

  • You can turn the camera dragging with the middle mouse bouton.

  • Press TAB to enter the Edit Mode for this cube.

  • Press A to unselect all the vertices.

  • Select the 4 upper vertices using the right mouse button.

  • Click on the blue arrow with the left mouse button.

  • Keep CTRL pressed to stick to the grid.

  • Lower the cube upper face of 1/2 unit. Press left mouse button again to stop.

  • Go to the View menu and click on Top. This will change your view and you will see the map from the top.

  • Press B to enter the box selection mode.

  • Select all the vertices of a side of the cube.

  • Press E to open the Extrude menu. Extrude the selected vertices on 1 unit.

  • Repeat the extrude step as shown on the video.

  • When done, press TAB to return to object mode.

  • Save your model in the client folder, under the models/maps folder.

Step 2: Giving a shape to your map

  • Reopen you map

  • Select you object

  • Enter edit mode by pressing TAB

  • Use the "face selection mode"

  • Select some faces, using the right mouse button

  • Move them, or extrude them, depending on the result you need (see the video) but remember to always stick to the grid using CTRL.

  • Return tu object mode using TAB

  • Save your file

Step 3: Apply a texture to your map

  • Your texture must have a power of 2 resolution (for instance, the one I use for this tutorial is 256x256)

  • Open your map with Blender

  • Select your object with the right click

  • Right click on the lower border, and split the screen verticaly

  • Open the UV editor on the second pane

  • Open your image in the UV editor

  • Select your object with the right click in the first pane

  • Enter edit mode for your object using TAB

  • Be sure to be in face selection mode if you are not (on the video I already am)

  • Select some faces

  • Press U to unwrap them

  • If your image is not selected in the UV editor, you can select it using the combobox

  • You should see your selected faces projected in the UV editor

  • In the first pane, select the Textured View Mode for your object, this allow you to see your modifications in live

  • Now you have to fix the place of your faces on the texture, for this, I use S to resize, G to grab, A to select/unselect, B to box-select

  • You can snap to pixel using the menu

  • Once done, return to object mode

  • You can see the textures are blur, you can fix that by disabling Mipmapping in blender. For this, drag the upper border, go to the OpenGL tab and disable Mipmapping

  • Save your file

Step 4: Export your map to Panda3D format

  • Download Chicken, the EGG exporter for Blender http://sourceforge.net/projects/chicken-export/

  • The installation of Chicken consist in unzipping the files in a folder, but the place of this folder differ depending of your OS, please refer to Blender documentation for your OS

  • Open you map with Blender

  • Select your object

  • Use File->Export->Chicken

  • The Chicken exporter should appear in one of your panes

  • Check 'use relative textures'

  • And click export, you should see a confirmation message

  • Your .egg map should appear in the same folder as your .blend map: client/models/maps/

  • If you have Panda installed, you can visualize your map with a tool provided by Panda

  • You can see on the video that our map is all blurry, this is due to wrong texture filters set by the exporter

  • You can fix it by openning the .egg with a simple text editor and change LINEAR to NEAREST

  • Your map is ready on the client side :)

Step 5: Adding your map server side

A map file on server side is a simple JSON file describing the map data:

    "name":  "custom001",
    "x":     9,
    "y":     9,
    "z":     5,
    "scale": [ 1.5, 1.5, 1.285714286 ],
    "offset": [ -4.5, -4.5, 0.0 ],
    "model": "custom001",
    "music": "10",
    "backgroundcolor1": [ 0.34, 0.24, 0.156, 1.0 ],
    "backgroundcolor2": [ 0.89, 0.734, 0.53, 1.0 ],
    "lights": [
        { "color": [ 0.8, 0.8, 0.8, 1.0 ] },
        { "color": [ 0.5, 0.5, 0.5, 1.0 ], "direction": [ 0.0, 45.0, -45.0 ] }
    "chartiles": {
        "1": [
               { "x": 2, "y": 0, "z": 0, "direction": 2 },
               { "x": 3, "y": 0, "z": 0, "direction": 2 },
               { "x": 4, "y": 0, "z": 0, "direction": 2 },
               { "x": 5, "y": 0, "z": 0, "direction": 2 }
        "2": [
               { "x": 2, "y": 8, "z": 2, "direction": 4 },
               { "x": 3, "y": 8, "z": 0, "direction": 4 },
               { "x": 4, "y": 8, "z": 0, "direction": 4 },
               { "x": 5, "y": 8, "z": 0, "direction": 4 }
    "tiles": [
        { "x": 0, "y": 0, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 1, "y": 0, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 2, "y": 0, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 3, "y": 0, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 4, "y": 0, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 5, "y": 0, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 6, "y": 0, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 7, "y": 0, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 8, "y": 0, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 0, "y": 1, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 1, "y": 1, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 2, "y": 1, "z": 1, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "convex_sw" },
        { "x": 3, "y": 1, "z": 1, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "slant_s" },
        { "x": 4, "y": 1, "z": 1, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "convex_se" },
        { "x": 5, "y": 1, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 6, "y": 1, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 7, "y": 1, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 8, "y": 1, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 0, "y": 2, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 1, "y": 2, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 2, "y": 2, "z": 1, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "slant_w" },
        { "x": 3, "y": 2, "z": 2, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 4, "y": 2, "z": 1, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "slant_e" },
        { "x": 5, "y": 2, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 6, "y": 2, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 7, "y": 2, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 8, "y": 2, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 0, "y": 3, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 1, "y": 3, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 2, "y": 3, "z": 1, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "convex_nw" },
        { "x": 3, "y": 3, "z": 1, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "slant_n" },
        { "x": 4, "y": 3, "z": 1, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "convex_ne" },
        { "x": 5, "y": 3, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 6, "y": 3, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 7, "y": 3, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 8, "y": 3, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 0, "y": 4, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 1, "y": 4, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 2, "y": 4, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 3, "y": 4, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 4, "y": 4, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 5, "y": 4, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 6, "y": 4, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 7, "y": 4, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 8, "y": 4, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 0, "y": 5, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 1, "y": 5, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 2, "y": 5, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 3, "y": 5, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 4, "y": 5, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 5, "y": 5, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 6, "y": 5, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 7, "y": 5, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 8, "y": 5, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 0, "y": 6, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 1, "y": 6, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 2, "y": 6, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 3, "y": 6, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 4, "y": 6, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 5, "y": 6, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 6, "y": 6, "z": 4, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 7, "y": 6, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 8, "y": 6, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 0, "y": 7, "z": 2, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 1, "y": 7, "z": 2, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 2, "y": 7, "z": 2, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 3, "y": 7, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 4, "y": 7, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 5, "y": 7, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 6, "y": 7, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 7, "y": 7, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 8, "y": 7, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 0, "y": 8, "z": 2, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 1, "y": 8, "z": 2, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 2, "y": 8, "z": 2, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 3, "y": 8, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 4, "y": 8, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 5, "y": 8, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 6, "y": 8, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 7, "y": 8, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" },
        { "x": 8, "y": 8, "z": 0, "walkable": true, "selectable": true, "depth": 0.0, "scale": 1.0, "slope": "flat" }


name = the name of your map
x, y, z = dimentions of the map, ours is 9x9, the z is the height*4+1. I think i'm going to remove these properties since the server can compute them itself using the tile data. But for now, you need to set them.
scale = controls the scale of your map model, don't touch this, there is a scale for original maps, and a scale for custom maps.
offset = place the center of the map on the (0,0) tile, on custom maps, it is [ x/2, y/2, 0.0 ], as our map is 9x9, we end with [ -4.5, -4.5, 0.0 ]
model = the filename of your .egg client side
music = the filename of your .ogg client side. Maybe I'm going to remove this too, as music should be chosen by the party creator
backgroundcolor1, backgroundcolor1 = the gradient of the background. this is RGBA, so [1,1,1,1] is white, and [0,0,0,1] is black
lights = a list of lights, if no direction specified it is an ambiant light, if a direction is specified, it is a direction light. Direction coordinates are HPR (see this for more info)

chartiles = the team placements. "1" and "2" are the team ID. This is not very smart, I think I'm going to change it too. direction goes from 1 to 4 and is the initial direction of the character.

tiles = the big part:
Our map is 9x9, so x goes from 0 to 8, and y too.
Remember that a wooden box is 4 z.
walkable is self explanatory
selectable is not used yet
depth is usefull for water, try to set it to 2.0
scale is the z scale of a slope, default to 1.0, but can be 2.0
slope is the shape of your tile, here are the available slopes:

  • flat

  • slant_n

  • slant_s

  • slant_e

  • slant_w

  • convex_ne

  • convex_nw

  • convex_se

  • convex_sw

  • concave_ne

  • concave_nw

  • concave_se

  • concave_sw

Save your .json file under the server/maps directory.

When you launch your developement server, the server finds the map in the maps folder and add it to the list of available map.
Congratulations, your map is complete!

Tethical / Installation instructions
May 12, 2011, 03:49:04 am
Installation instructions for the testers and gamers (All OSes)

Get the panda3d runtime for your OS and install it.

Download the game client and launch it.

In the actual state of the developpement, the server may be offline due to bugs or security issues, find me on the support channel (Kivutar @ #ffh on irc.esper.net) to get the server running.

Installation instructions for the developpers (Windows)

1. Download Tethical's python source code from the version control website (github): Tethical (zip is 27.7mb as of this writing).
2. If you have Panda3d installed on your machine, you can run the game by creating .BAT files (Windows command-line batch command files), one for the server program and one for the client program (see below for .BAT set up).
3. If you don't have Panda3d installed, get it from Carnegie Mellon University's website for their Panda3d project: Panda3d Software Development Kit (version 1.7.2 @ 84.1mb as of this writing).
5. From this point, you can run the client and server from the installation of Python that comes with Panda3d's SDK:
      1. Navigate to where you unzipped Tethical.
      2. Navigate to the "server" directory.
      3. Create a new file "start.bat".
      4. Paste this into the file:
C:\Panda3D-1.7.2\python\ppython.exe main.py
      5. Copy and paste that file to the "client" directory where you unzipped Tethical.
      6. At this point, place a shortcut to those two .bat files on your desktop (or wherever is convenient to you).
      7. If you want to see errors when they happen (because they cause the program to quit), put "pause" on a the second line of the .BAT files.
6. Open the server (via start.bat).
7. Open the client twice (via the other start.bat).
8. Log in by using the same username and password (a, a, for example; or, b, b, for example).
9. Create a room in one client, join it in the other.

Installation instructions for the developpers (Ubuntu Maverick)

Add the following PPA to your sources.list:

deb http://ppa.launchpad.net/panda3d/ppa/ubuntu maverick main

Install the dependancies

sudo aptitude install panda3d panda3d-runtime libdancer-perl libmodern-perl-perl libjson-perl

Get the source code

git clone git@github.com:Kivutar/tethical.git

Launch the server

cd tethical/server
python main.py

Launch the client

cd tethical/client
python main.py

Natty users should launch the client using python2.6
Tethical / Tethical, online FFT clone
February 25, 2011, 04:38:42 am
    Hi FFT hackers,

    I started writting an FFT clone.

The goal

The goal is not to make a complete game, it's too much work for me alone. But I think I could release a small playable demo.
The game would be online and PvP, no AI for now, no classes for now, something very minimal and easy to code.
However, it is an open source project so I try to make things as reusable as possible. So the project may be usefull to people wanting to code a more RPG-oriented FFT, by provinding classes for battle mechanics and the graphical engine.

Technical details

Temporary server written in Perl with Dancer. (Of course I plan to drop HTTP wich is not suitable for the project.) Server written in python using the datagram protocol.
Client written in python using Panda3D engine.
Source code on github https://github.com/Kivutar/tethical
Website: http://tethical.kivutar.me

I'm currently working on

  • Effects

  • Fonts

  • AI

  • Official map exporter

  • Keyboard support

  • GUI

  • New maps

  • Disabled menu items

  • Battleflow bug

  • Direction buttons

  • Baked shadows

  • Gameplay

  • Too much freedom in camera movements

  • Character animation bug

  • Using CEGUI, a better GUI Switched back to DirectGUI for now

  • Server not supporting xyz indices for tiles

  • Ugly textures

  • Connecting client and server

  • Contacting people like the ffh guys, dbp, gomtuu, Mephs, Calvin Chen, ebonrai, galaxymage team, sofakng, onimatrix...

  • Replacing copyrighted content from the client

  • Nice 2 color background



  • Special effects


  • Formation screen
  • Bugfix for the camera ratio
  • Code refactoring
  • New keyboard config
  • Typo fix in Lijj Direction.py
  • Keyboard configuration in the config file
  • Remove kivu and lirmont folders server side too
  • Code cleaning
  • Configuration variables for the keyboard
  • Code cleaning
  • Apply the last two patches on Lijj folder
  • Fix the hand texture
  • Remove the characters on attack_check image, replace them with the fixed font
  • Fixed many issues with the regular font: color, shadow, line height, spacing
  • Removed kivu and lirmont folders for now, they add too much work
  • Custom direction chooser for Lijj game
  • Added modularity for the code
  • Config files, multigame support
  • Forgot to add particle textures and config files
  • 12 palettes for Lijj's girl knight
  • Particle effects in the battle
  • More UVmapping for the map 007
  • Finaly fixed sprite resolution
  • Battle cursor pointer
  • Remove the action preview when the battle is complete
  • A lot of improvements in the attack GUI flow
  • Attack check message placement fix
  • gitignore
  • Resolution fix
  • Double resolution sprite + per team palettes
  • Daylight version of the custom map 006
  • Remove the auto shader to temporary fix the background bug
  • UV mapping improvements on map 007
  • Custom texture for the first custom map
  • Better texture for the zones
  • Textures for attackables and walkables tiles
  • 40x40px cursor and shadow
  • Finished to declare the tiles for the second map
  • Second map improvements
  • The new map exported to egg
  • Map rename
  • UV texture for the second map
  • Map improvements
  • Map cleaning
  • A new map almost modelized
  • Point light support
  • Higher walk animation speed when moving
  • Some modeling experiments
  • Fixed the other json map files with the new syntax for multiplayer



  • Some job informations

  • Compute CT using character speed, according to the Battle Guide Mechanic...

  • Better job files from the Battle Guide Mechanics

  • Compute true character stats using their job and level

  • True bare handed damage formula

  • A new small font + display true brave and faith

  • Some improvements on the return to party list code

  • Removed the useless STATS datagram

  • Fix for the canact bug


  • Unload the map chooser models

  • Added the small jump animation

  • Center the intro animation on the map bounds center

  • Code cleaning

  • Refresh button texture

  • Added some imagemagick scripts to export sprites to tethical format

  • Display the sprite corresponding to the job and gender of the character


  • The new map chooser


  • Display battle coordinates with the official font

  • Allow characters to move through death bodies

  • Allows characters to move through the tiles containing a team member

  • Server code cleaning to prepare move algorithm improvements

  • 4th custom map model

  • Send only usefull informations to fix the lag on character turn

  • Code cleaning

  • Made the help message cancelable


  • Added UV coordinates to all slope models, so they are texturable

  • Moded the client code to switch cursor shape according to terrain shape

  • Color the cursor in red when on an unwalkable tile


  • I finally understood how panda fonts work

  • Made a multicolored (black & white) font

  • Display character's stats during the battle


  • A new custom map

  • Add an unfinished custom map

  • Fixed the map exporter to not place characters in water

  • Allow more parties to be created

  • Connect server only when credentials are provided

  • Window title

  • Return to party list after a battle

  • Battle Complete datagram

  • Fix the Game Over screen

  • Code cleaning


  • Updated the README to reflect the server switch

  • Raise TCP header size to allow bigger maps

  • Change default port to 3001

  • Fix damages

  • Command line argument to specify server port

  • Prevent panda to open a window for the server

  • Renamed some files

  • Server rewritted to python, switch to the datagram protocol

  • Readme

  • Attackables algorithm takes height into account

  • Move algorithm takes jump stat into account

  • Remove useless code

  • Add genericity so the game can handle both original and custom maps


  • Export complete .obj maps with flipped normals

  • First steps in map logic export


  • First official map exported, special thanks to lirmont who wrote the exporter!


  • Fix for the tile ratio bug

  • Fix for the sprite animation bug

  • Conditions for winning screen

  • Congratulations screen

  • Game over screen


  • Fixed the terrain aspect ratio

  • Fixed the walk speed

  • Color fix for the direction chooser textures

  • Move and Attack check messages

  • Hide the black overlay when selecting a character to attack

  • Fix cursor Z

  • Clear the blue zone before the character starts to walk

  • Textured cursor


  • New map

  • Keyboard driven battle menu


  • Battleflow bugfix: clear attackable tiles on cancel and any special tile...

  • Fix the no walkable battleflow bug + keyboard driven cancel menu

  • Keyboard driven direction chooser

  • Keyboard driven help messages

  • Code refactoring: tile chooser keyboard events

  • Code refactoring: battle phases


  • Display character name, job and sign in the character status

  • Right sound effects for the lobby widgets

  • Add camera movements for move and attack action in passive phase

  • Better use of the lobby tasks to poll the server

  • Add a 1sec delay before first character start

  • Lobby code refactoring : moved all the GUI related code to the GUI module

  • Keyboard support for the tile chooser


  • First steps in displaying character status


  • Better battle introduction, with camera moving around the map

  • Help messages for the direction and tile choosers

  • Fixed the wheel background resolution, it was blurry

  • I fixed the camera height, it was too low


  • New lobby GUI

  • FFT font

  • Ramza sprite


  • Better party selection list

  • Less hardcoded accounts, the way is open to store accounts in a database

  • Walk speed fixed


  • Automaticaly open active character's menu

  • AT sprite

  • Lock camera control and tile highlighting when needed

  • Direction selector

  • Automaticaly open direction selector when character has already moved or acted


  • BGM

  • Sound effects

  • Basis for a better GUI


  • Better aspect ratios

  • Better camera angles

  • Unique texture for maps

  • Transparency bug fixed

  • Character shadows


  • Characters can attack, lose HP and die

  • Lot of code cleaning

  • Added 'weak' and 'dead' statuses to the sprites

  • Some work done on a new GUI wich is way better


  • Battle moves shared accros the server

  • Better camera movements

  • Code cleaning


  • 2 color background

  • keyboard driven camera handler


  • Random team generated

  • Full support of xyz coords server side

  • Draw walkable tiles sent by the server


  • Players can create parties

  • They can join parties too

  • Removed formation screen for now, a random team will be generated


  • Original map + original but ugly textures

  • Coords displayed top right of the screen

  • GUI to connect the game server, and create a party


  • Uncomplete camera movements

  • Character movements keeping right direction

  • Exported official map using Ganesha, but texture issue

See also