To begin, you must first locate the game directory. If you downloaded a native version of the game (such as for windows), then it should be the filosis_data folder. If you downloaded the lightweight raw html version of the game, then you have only the game directory.
Open the mod folder and create a new folder with the name of your mod. Create a text file inside your mod folder named "script.js". This file will contain the script contents of your mod, but for now we just want to know if it loaded. Write the following into the file:
alert("Hello World");
In order to load the mod, we must also edit index.html in the game directory. Find the area where mods are loaded. By default, the Draco mod should be loaded. Add a line for your mod below it, like so:
<script src="mod/draco/script.js">
<script src="mod/example/script.js">
Now when you run the game, you should be greeted with a message saying "Hello World".
Every mod should have a unique name. All functions, objects, and variables introduced by your mod should either be prefixed with your mod's name, or contained within a parent object named after your mod. This should prevent name collisions with the game code as well as other mods.
An example of how to properly encapsulate your mod:
//contained in an object
var examplemod = {};
examplemod.value = "example";
examplemod.func = function(){
console.log( examplemod.value );
}
//prefixing
examplemod_value = "example";
function examplemod_func(){
console.log( examplemod_value );
}
Most types of mods will require new assets to be added to the game. Usually images. Assets can be loaded using the preload_mod hook. Some common types of assets that can be loaded are images, spritesheets, and audio.
preload_mod.push(function(){
game.load.image('example_image', 'mod/example/image.png');
});
An NPC is made up of three main components, the char, the port, and the speaker. The char is the overworld sprites, the port is the portrait, and the speaker is an object which contains information about the NPC's name, portrait, and voice.
For example, let's say we want to add Legionella:
preload_mod.push(function(){
game.load.spritesheet('legionella', 'mod/legionella/char.png', 20, 25);
game.load.image('legionella_port', 'mod/legionella/port.png');
});
speakers.legionella = {display:'Legionella', default:'legionella_port', voice: 'voice8' };
scenario_mod.push(function(){
if (switches.map != 'earth3') { return; }
var legionella = new NPC(31*TS, 17*TS, 'legionella');
legionella.face("down");
legionella.interact = function(){
say('legionella', "Hello there!");
};
});
preload_mod.push(function(){
game.load.image('example_scorpion', 'mod/example/scorpion.png');
});
Monster.types.example_scorpion = {
name: 'Scorpion',
key: 'example_scorpion',
skills:[skills.poisonstrike, skills.strike],
drops:[
{item:'bugbits', chance:100, quantity:2},
{item:'bugbits', chance:50, quantity:1},
{item:'starpuff', chance:3, quantity:1}
],
xp: 60,
hp: 80,
atk: 80,
def: 50,
speed: 100,
attributes: ['poison', 'carnivore']
}
Attributes do nothing on their own. Instead, they are used for special interactions in other parts of the code. For example, poison makes poisonous attacks less effection against the monster, and carnivore allows it to be distracted with Meat.
A monster type can be supplied an AI function to make it choose skills more smartly. The AI function can return a skill or null. If it returns null, normal skill selection based on the skills property will be used.
Monster.types.example_scorpion.ai = function(){
// uses poisonstrike if not all enemies are poisoned.
// otherwise uses strike.
var enemies = enemy_list();
var poisoned = 0;
for (var i in enemies){
if ( enemies[i].has_buff ( buffs.poison ) ) {
++poisoned;
}
}
if (poisoned < enemies.length){
return skills.poisonstrike;
}else{
return skills.strike;
}
}
After you've created your monster, you need to set up an encounter with it. An encounter is a group of monsters that can be battled.
encounter.example_scorpion ={
monsters : [
{id:'example_scorpion', x:2, y:2, l1:0, l2:Infinity},
{id:'example_scorpion', x:-2, y:0, l1:0, l2:Infinity}
]
};
l1 and l2 are the level range of the monster. 0 and Infinity will make the monster always scale to the player's level.
There are a few other less-used properties you can use as well.
encounter.example_scorpion ={
monsters : [{id:'example_scorpion', x:0, y:1, l1:6, l2:Infinity}],
// modifies the level of all monsters in the encounter.
// In this case, increasing their level by 2.
lmod: 2,
// function called when player wins
onvictory: function(){
switches.example_victory = true;
},
// function called when player loses.
ondefeat: function(){
switches.example_victory = false;
},
// forces the encounter to use a particular background.
bg: 'castle'
};
By default after you lose you will be sent back to the title screen. However this behavior can be overridden if needed.
end_battle_2 = override_before(end_battle_2,function(){
if( battle.encounter.allowdefeat ){ battle.result="allowdefeat";}
});
encounter.joki.allowdefeat=true;
There are a few ways to initiate battle. The simplest way is with an NPC.
// start battle through dialogue
chikun.interact=function(){
say ('chikun', "Are you ready for an epic battle?");
say (function(){ start_battle(encounter.chikun); });
}
// start battle by touching NPC
chikun.battle = encounter.chikun
Mobs are what I call the little black and white enemies that run around the overworld.
preload_mod.push(function(){
game.load.spritesheet('example_mob_scorpion', 'mod/example/mob_scorpion.png', 16, 17);
});
Mob.types.example_scorpion = {
pattern: 'basic',
encounters: ['example_scorpion'],
key: 'example_mob_scorpion'
};
The mob may have the following properties:
After you've created a new type of mob, the easiest way to add it to an existing map is by creating a spawner.
spawn_controller.spawners.push({
x:32*TS,
y:32*TS,
properties:{
type:"example_scorpion",
radius: 12
}
});
// load spritesheet for skill animation
preload_mod.push(function(){
game.load.spritesheet('example_shadowflame_animation', 'mod/example/shadowflame.png', 42, 42);
});
// set up skill animation
animations.example_shadowflame={
sprite: 'example_shadowflame_animation',
frames: [0,1,2,3,4,5,6],
anchor: [0.5,0.5]
};
// create the skill
skills.example_shadowflame = new Skill({
id: 'example_shadowflame',
name: "Shadow Flame",
desc: "Deals damage and also causes damage over time.",
sfx: 'flame',
animation: 'example_shadowflame',
sp: 100,
attributes:['magic','fire','attack'],
target:'enemy',
action:function(){
damage_target(75);
battle.target.inflict(buffs.example_shadowflame);
}
});
Skills can also be given a property named aitarget which is used for the ai to smartly select a target.
skills.example_shadowflame.aitarget = function(){
var enemies=enemy_list();
list=[];
for(var i in enemies){
var enemy = enemylist[i];
if ( enemy.has_buff ( buffs.example_shadowflame ) ) {
list.push(enemy);
}
}
if (list.length===0){ list = enemies; }
battle.target = list[Math.floor(Math.random()*list.length)];
};
Buffs are status effects, usually inflicted by skills or items. They can be both positive and negative. A battler can only have up to 5 buffs inflicted at once.
buffs.example_shadowflame = {
name:'example_shadowflame', // name should be same as id.
start:function(){
this.duration=3;
},step:function(){
this.damage(20 * deltam);
this.duration -= deltam;
if(this.duration<=0){ this.remedy(); }
},
negative:true
};
costumes.marb.pirate = { name:'Pirate', csheet:'piratemarb_char',
bsheet:'piratemarb_battle', bframe:[0,1,2],
psheet:'piratemarb_port' };
Changes which sprite sheet is used for the face. Useful if the costume involves an eyepatch or heterochromia which can't be accomplished through recoloring.
example:
costumes.marb.pirate.fsheet = 'piratemarb_face';
Recolors the face. Useful if the costume changes the eye or skin color. This property should be a nested array, with a list of the original colors and a list of the new colors.
costumes.ebby.blueeyes.frecolor =
[ [0xe64747, 0xffce3c], [0x435bfd, 0x43b1fd] ];
Maps are made using Tiled Map Editor. Super Filovirus Sisters was made using an old version of Tiled, so some newer features are not supported.
Rather than create a new map from scratch, You can modify an existing map so that the layers and tilesets are already set up properly.
Every map should have exactly two layers. a tile layer named "tile_layer", and an object layer named "object_layer".
The map should use embedded tilesets. After the tileset is loaded by the game, _tiles will be appended to its name (except for the tileset named tiles). You should edit the tileset properties to set up collision etc.
preload_mod.push(function(){
load_map('example_map', 'mod/example/map.json');
});
Objects placed in the map will only appear in-game if they are a type the game recognizes. Here are the types, and how to use them.
//Create new category.
pentagrams["Example Zone"]={};
/*The key must be the name of the map and checkpoint,
separated with an underscore. Here, example_map is the name of
the map, and cp0 is the name of the checkpoint.*/
pentagrams["Example Zone"].example_map_cp0 = "Example Destination";
Every map should also have a mapdata object that describes its properties.
Here is a simple example:
mapdata.example_map = {
edges: 'normal',
outside: true,
spawning: Mob.types.slime,
bg: 'forest'
};
Here is a list of the possible properties, and what they do:
If set to 'normal', tiles beyond the edge of the map will be empty. If set to 'clamp', the tiles on the edges of the map will be repeated beyond the edge. Default is normal.
If a map is outside, empty tiles will be water instead of walls.
Determines what kinds of mobs spawn in the map. Can also be a function that returns a type of mob. The function is passed the tile the mob will be spawning over as a parameter. This can be used to make different mob types spawn over different types of tiles.
In milliseconds, determines how often mobs will spawn in the map. Default is 7000.
Determines how many mobs can be spawned at once.
A function that determines how the reflection of the soul cluster (aka the sun) should be displayed. If the function returns true, and there is a node on the map named 'sun', then the sun's reflection will dynamically move relative to the location of that node.
mapdata.example_map.sun=function(){
//this will make the sun invisible.
this.scale = {x:0, y:0};
//places a large sun in the center of the screen.
this.x = game.width/2;
this.y = game.height/2;
this.scale = {x:1.5, y:1.5};
//this makes the sun move dynamically.
return true
};
The name of the battle background that will be used in this map. Can also be a function which returns this value. The function is passed the terrain of the tile the mob was over as a parameter.
mapdata.example_map.bg = function(terrain){
if (player.water_depth>0 && terrain == 'water'){
return switches.soulcluster ? 'water' : 'water_night';
}
return switches.soulcluster ? 'forest' : 'forest_night';
};
I have provided a few hooks into the code to make modding the game a bit easier. These hooks are arrays of functions. By adding a new function to the array, your function will be called by the game.
Here is a list of the existing hooks:
Called when initializing the game, after the initial preload has finished.
Called once every frame. Please use this sparingly.
Called in the preload phase. Used to specify files to be preloaded.
Called after creating the gui.
Used for saving and loading game options.
Used for specifying new input keys.
Called after the map is loaded. This is where you should create NPCs and objects to populate the map.
These hooks are a bit different from the others. Instead of containing functions, they contain menu items.