Adding research data¶
This document will explain the steps behind adding field research data to FreeField.
Objectives¶
All available objectives in FreeField are stored in the objectives.yaml
file. Objectives in this file appear in objective selection menus in the order
they are presented in this file. When adding a new objective, should ensure that
it is grouped with other similar objectives, i.e. don’t just throw it at the end
of the file. E.g. win_raid
is placed underneath battle_raid
, and
together with win_gym
and battle_gym
, because these objectives are
similar in nature.
The structure of a research objective entry is as follows:
objective_id:
params:
- param1
- param2
- ...
categories:
- main_category
- parent_category
- ...
Objective IDs¶
Each objective has a unique ID that is used to identify that specific objective internally. The objective ID should reasonably describe the objective that you are adding. Feel free to look at existing objective definitions in the file, and name your objective similarly to those that are already in the file.
For example, “Catch a Pokémon” has the ID catch
. “Catch a Pokémon with
Weather Boost” has the ID catch_weather
.
All objective IDs are lowercase, with words separated by underscores.
Parameters¶
Many research objectives take one or more parameters. For example,
catch_type
(“Catch N X-type Pokémon”) takes two - the number of Pokémon to
catch, and the type of Pokémon to catch. These parameters should be listed in
the params
key in the objective’s definition. If the objective takes no
parameters, params
is declared as an empty array like this:
objective_id:
params: []
categories:
- main_category
- parent_category
- ...
You can only use parameters that have been implemented in FreeField. See List of parameters for a list of all parameters you can use.
If you use the quantity
parameter, then that should be the last element in
the parameters list, if multiple elements are present, for internationalization
reasons.
Categories¶
Every objective has one or more assigned categories. Categories are used to group similar objectives together, and also serve as the declaration of which icon should be used to represent each objective on the map and elsewhere. Usage of categories is explained in Icons and categories.
Internationalization¶
You must internationalize and localize to English any research objectives you submit to FreeField in the localization files. These are located near the top of the files, underneath the species typing localizations. You must, as a minimum, add the tokens to the en_US.ini file, though if you speak another language, feel free to add them to the localization files of other languages you speak.
The tokens you need to add are:
objective.<objective_id>.singular = "Perform an action"
objective.<objective_id>.plural = "Perform {%n} actions"
The two tokens represent the singular and plural versions of the human-readable string that represents the research objective.
You need to add replacement tags for any parameters required by the objective.
This is done by placing {%n}
tags in the string at the locations that the
parameters are to be substituted in, where is chosen by the order in
which the parameters are declared in the params
field of the objective
definition. For example, consider an objective that has defined its params
array like this:
params:
- species
- type
- quantity
In this case, species
is assigned to {%1}
, type
to {%2}
, and
quantity
to {%3}
.
Note
Make sure you declare these internationalization tokens in the same place as the tokens declared for the other objectives. You can find them quickly by searching the file for “objective.”
Common objectives¶
In order to make it easy for users to report research, FreeField maintains a list of research objectives that are commonly reported, along with the specific Pokémon species those research tasks can award if reported with a Pokémon encounter reward. These objectives are stored in the common-tasks.yaml file.
An entry in the file may look like this:
-
# Power up Pokémon 5 times
type: "power_up"
params:
quantity: 5
encounter_species:
- 1 # Bulbasaur
- 4 # Charmander
- 7 # Squirtle
Each entry has a type
, a list of params
and an optional list of
enconter_species
.
type
- This is the ID of the objective that corresponds to the common task, as declared in the objectives.yaml file (see Objectives above).
params
- This is a list of parameters required by the objective specified in
type
. The value corresponds to the value of the parameter in the research objective. In the example above, the objective ispower_up
i.e. “Power up Pokémon [n] times” where , assigned to thequantity
parameter, which is what is required by the definition ofpower_up
. encounter_species
- This field is optional, though should be present if the research task offers Pokémon encounters as a possible reward. If present, this is a list of one or more Pokémon species that are awarded upon completion of the research task.
Note
The species parameter accepts Pokédex numbers as input. To make it easier for readers to understand which species are required or awarded by the research task, you should comment the name of the Pokémon species after the Pokédex number (as seen in the example above with Bulbasaur, Charmander and Squirtle).
Rewards¶
All available rewards in FreeField are stored in the rewards.yaml
file. Rewards in this file appear in reward selection menus in the order they
are presented in this file. When adding a new reward, ensure that it is grouped
with other similar rewards, i.e. don’t just throw it at the end of the file.
E.g. great_ball
is placed underneath poke_ball
, and together with
ultra_ball
, because these objectives are similar in nature.
Categories of rewards should be ordered such that those rewards that most people are likely to report are placed at the top of the file. In FreeField, Pokémon encounters are at the top, while Rare Candies are second, because of their popularity and desirability for players.
The structure of a research reward entry is as follows:
reward_id:
params:
- param1
- param2
- ...
categories:
- main_category
- parent_category
- ...
Reward IDs¶
Each reward has a unique ID that is used to identify that specific reward internally. The reward ID should reasonably describe the reward that you are adding. Feel free to look at existing reward definitions in the file, and name your reward similarly to those that are already in the file.
For example, “Poké Balls” has the ID poke_ball
. “Potions” has the ID
potion
.
All reward IDs are lowercase, singular, and with words separated by underscores.
Parameters¶
Many research rewards take one or more parameters. The majority of rewards offer
some kind of quantity of itself - for example, great_ball
(“Great Balls”) -
the number of Great Balls that is awarded by a research task must be specified.
These parameters should be listed in the params
key in the reward’s
definition. If the reward takes no parameters, params
is declared as an
empty array like this:
reward_id:
params: []
categories:
- main_category
- parent_category
- ...
You can only use parameters that have been implemented in FreeField. See List of parameters for a list of all parameters you can use.
If you use the quantity
parameter, then that should be the last element in
the parameters list, if multiple elements are present, for internationalization
reasons.
Categories¶
Every reward has one or more assigned categories. Categories are used to group similar rewards together, and also serve as the declaration of which icon should be used to represent each reward on the map and elsewhere. Usage of categories is explained in Icons and categories.
Internationalization¶
You must internationalize and localize to English any research rewards you submit to FreeField in the localization files. These are located underneath the objectives localizations and above the category localizations in the files. You must, as a minimum, add the tokens to the en_US.ini file, though if you speak another language, feel free to add them to the localization files of other languages you speak.
The tokens you need to add are:
reward.<reward_id>.general = "Item"
reward.<reward_id>.singular = "1 Item"
reward.<reward_id>.plural = "{%1} Items"
The two latter tokens represent the singular and plural versions of the
human-readable string that represents the research reward. The former token,
general
, represents the singular version of the string, but without the
number “1” that denotes quantity of rewards.
You need to add replacement tags for any parameters required by the objective.
This is done by placing {%n}
tags in the string at the locations that the
parameters are to be substituted in, where is chosen by the order in
which the parameters are declared in the params
field of the reward
definition, in the same way they are chosen for objectives. In most cases,
rewards only have the quantity
parameter defined in their params
arrays
like this:
params:
- quantity
In this case, quantity
is assigned to {%1}
.
Note
Make sure you declare these internationalization tokens in the same place as the tokens declared for the other rewards. You can find them quickly by searching the file for “reward.”
Research parameters¶
Objectives and rewards are both declared with a list of required parameters.
When an objective or reward definition lists a certain parameter in its
params
array, the user will be required to input data for that parameter
when submitting research. Parameters are things such as the quantity of items
rewarded by a task, or a particular species of Pokémon that must be e.g. evolved
in order to complete a particular objective.
FreeField implements parameters using classes that define each parameter’s behavior. This includes how data is read from and displayed to the user, how the data for a parameter is stored in the database and configuration file, and input parsing and validation functions for data that is submitted by research reporters. All of these parameter classes are implemented in the research.php file.
Parameters must additionally be registered in the PARAMETERS
constant in
research.php, and internationalization tokens must be created for placeholders
and labels. The sections below will assist you in setting up a class,
registering it, and adding the required internationalization tokens.
Implementing a parameter class¶
When you implement a research parameter, it will become available for use as a
requirement by objectives and/or rewards. Classes are placed in the research.php
file. The definition of the quantity
parameter follows below, and will be
used to help you implement your own parameter by way of example.
/*
Adds a number box to the field research box prompting the user for the
quantity of items awarded in a reward/quantity of catches required for
a catch quest, etc. This parameter is stored as an integer
*/
class ParamQuantity {
public function getAvailable() {
return array("objectives", "rewards");
}
public function html($id, $class) {
return '<p><input id="'.$id.'" class="'.$class.'" type="number" min="1"></p>';
}
public function writeJS($id) {
return 'var val = parseInt($("#'.$id.'").val());
if (isNaN(val)) return null;
return val;';
}
public function parseJS($id) {
return '$("#'.$id.'").val(data);';
}
public function toString($data, $allParams) {
return strval($data);
}
public function toStringJS() {
return 'return data.toString();';
}
public function isValid($data) {
return is_int($data) && $data >= 1;
}
}
The class must implement several required functions. These are explained in detail below.
getAvailable()
¶
This function must return an array that has either one of, or both, of the following strings:
- “objectives”
- If this string is in the returned array, this parameter will be made available as a parameter for research objectives. If not included, this parameter will not be possible to use with objectives.
- “rewards”
- If this string is in the returned array, this parameter will be made available as a parameter for research rewards. If not included, this parameter will not be possible to use with rewards.
Hint
FreeField separates between objective and reward parameters to cut
down on the size of the HTML response on the map. If a parameter is
loaded for both objectives and rewards, it is rendered twice on the
page - this does not matter much for parameters with a small output
size, such as quantity
, but for parameters with larger output
sizes, e.g. species
, this can have a big effect on the size and
complexity of the output HTML. Therefore, ensure that you only declare
the scopes that you actually need the parameter to be valid for - in
most cases, it only has to be valid for objectives.
html($id, $class)
¶
This function must return an HTML string with input boxes that allow the user to
input data for this parameter. For the quantity
parameter, this is just a
simple numerical input box. For type
, this input consists of three rows of
select boxes that allow the user to select a Pokémon type.
This function is passed two variables, $id
and $class
. You must use
these variables as the element ID and class names of your input box(es)/select
box(es) (i.e. not the parent <p>
or other tags). If you have multiple
input elements, you can append e.g. “-1”, “-2” etc. to the end of the ID. You
can specify additional class names, but the classes provided by $class
must
be present, otherwise the input will not function correctly.
writeJS($id)
¶
This function must return a JavaScript function body that fetches the value of
the input elements returned by html()
above and writes/parses it to the
correct data format. The function should return this value, either as a string,
an integer or floating point number, or as an array of any of these. If the
returned value is null
or an empty string, the value will be considered
invalid and the user will not be allowed to submit research until the field has
been populated with data. This can be used to e.g. check if an integer value is
NaN
and return null
in that case.
The $id
parameter is the same as the one that is passed to the html()
function, and should be used to find and select the correct input element(s) to
read data from.
The JavaScript function for quantity
looks like this:
var val = parseInt($("#<ID>").val());
if (isNaN(val)) return null;
return val;
This function reads the value of the input box with the given ID and attempts to
parse it to an integer. If it fails, it returns null
(i.e. field is empty or
invalid), otherwise it returns the value.
parseJS($id)
¶
This function must return a JavaScript function body that does the inverse
operation of writeJS()
, i.e. it takes input data, parses it and fills it
into the input boxes with the given $id
.
The JavaScript function is passed a parameter data
which contains the data
in the same format as the function in writeJS()
returns. Use this data to
set the value of input boxes in the DOM.
The JavaScript function for quantity
looks like this:
$("#<ID>").val(data);
It simply takes the data
object, which is an integer, and sets it as the
value of the input box with the given ID.
toString($data, $allParams)
¶
This function should convert the $data
(which is in the format returned by
writeJS()
) to a human-readable string suitable for substitution into the
localized string before being returned to the user. For example, the objective
“Catch a [Pokémon Species]” (e.g. “Catch a Bulbasaur”) uses this function to
return the actual string “Bulbasaur,” “Charmander,” etc. for substitution into
the “Pokémon species” tag. If you return strings, these must be localized by
your implementation of this function. The quantity
parameter only needs to
convert the integer $data
to a string value, hence a simple strval()
is
sufficient. For a more complex example that includes localization, take a look
at e.g. the implementation for the species parameter.
The $allParams
parameter is an array with all parameters passed to the given
research objective or reward that your parameter is used for. The array can e.g.
look like this:
$allParams = array(
"quantity" => 5,
"species" => [1, 4, 7]
);
In most cases, you can ignore this parameter. It can be used to determine
whether the singular or plural form of an internationalized string should be
resolved. This is currently only used in practice in encounter_item
through
its parent class ParamReward
(see here),
to determine whether the singular or plural form of the name of the required
item should be used, depending on the quantity of items required to use, through
checking the quantity
parameter.
toStringJS()
¶
This function should return a JavaScript function body ported from the PHP code
in toString()
above. The function is passed data
and the allParams
object, and should return the same string representation as toString()
. This
function is used client-side, while toString()
is used server-side for e.g.
Research task information in webhook payloads.
isValid($data)
¶
This function should perform server-side input validation on $data
before it
is accepted and stored in the database. There is no guarantee that $data
is
of the same format as the object returned from writeJS()
, hence you must
ensure that the format is acceptable as well. The validation function of
quantity
checks whether or not the input data is an integer, and also checks
if it is 1 or higher (it makes no sense to report 0 or a negative quantity of an
objective or reward). If you return an array from writeJS()
, check that it
is an array with is_array()
, and that it has a valid number of elements. If
the output of writeJS()
is a string, check that $data
is a string, etc.
Registering the parameter class¶
In order for a parameter class to be usable, it must be registered in the
PARAMETERS
array in the Research
class in research.php. The array may
look like this:
const PARAMETERS = array(
// CLass mappings for each parameter
"quantity" => "ParamQuantity",
"min_tier" => "ParamMinTier",
"species" => "ParamSpecies",
"type" => "ParamType",
"encounter_item" => "ParamEncounterItem"
);
Assign your parameter an ID that reflects the kind of data that it holds. For
example, the quantity of items rewarded by a task, or quantity of Pokémon needed
to complete a task, etc. has been assigned the ID quantity
. The parameter
that stores the type of Pokémon that must be caught/evolved/etc. for a task is
called type
. The name you choose must be in snake_case and is the key of
your parameter in the PARAMETERS
array.
The value of this key is the name of the class you implemented above. The
class name should, for consistency, begin with “Param,” and be followed by the
ID of the parameter converted to UpperCamelCase. For example, min_tier
becomes ParamMinTier
.
Adding internationalization tokens¶
You need to define two internationalization tokens in the localization files for your parameter. These are located above the Pokémon species names list, and underneath the objectives, rewards and categories in the files. You must, as a minimum, add the tokens to the en_US.ini file, though if you speak another language, feel free to add them to the localization files of other languages you speak.
The tokens are:
parameter.<parameter_id>.label = "Label of parameter"
parameter.<parameter_id>.placeholder = "[Placeholder]"
The label
sub-key should contain a short human-readable label that describes
the parameter. E.g. for quantity
, this is simply “Quantity.” This is the
string that appears next to the input boxes when users report research with the
parameter.
The placeholder
sub-key is displayed in the list of research objectives
and/or rewards for objectives/rewards which implement the parameter, but for
which no value is currently known. E.g. for quantity
, the placeholder is
“[n].” This means that the parameter could be displayed as e.g. “Catch [n]
Pokémon” if no quantity is currently known for that task.
Note
Make sure you declare these internationalization tokens in the same place as the tokens declared for the other parameters. You can find them quickly by searching the file for “parameter.”
List of parameters¶
The following is a list of all available parameters. When you add a new parameter type, please document it here.
quantity
¶
Quantity of items rewarded by a task, quantity of Pokémon to catch to complete an objective, etc. Can be used for both objectives and rewards.
The data of this parameter are stored as integers that are 1 or greater.
min_tier
¶
Minimum tier of a raid that must be completed (used in “Win a level [x] or higher raid”). Can only be used for objectives.
The data of this parameter are stored as integers that are in the range 1 through 5.
species
¶
One or more species of Pokémon that must be caught, evolved, etc. up to three different species. Can only be used for objectives.
The data of this parameter are stored as arrays of integers that are at least one, and up to three elements long. Each element is in the range 1 through the Pokédex number of the last Pokémon in the latest generation of Pokémon that are currently implemented in FreeField. The numbers correspond to the Pokédex number of Pokémon.
type
¶
One or more types of Pokémon that must be caught, evolved, etc. up to three different types. Can only be used for objectives.
The data of this parameter are stored as arrays of strings that are at least
one, and up to three elements long. The elements are picked from TYPES
:
const TYPES = array(
"normal", "fighting", "flying",
"poison", "ground", "rock",
"bug", "ghost", "steel",
"fire", "water", "grass",
"electric", "psychic", "ice",
"dragon", "dark", "fairy"
);
encounter_item
¶
An item that can be used on the Pokémon encounter screen (i.e. balls and berries). This is used for the “Use a [Item] while catching Pokémon” objective. Can only be used for objectives.
The data of this parameter are stored as strings, selected from the set of all rewards that are assigned to the categories “ball” or “berry” in the rewards.yaml file.
Icons and categories¶
Objectives and rewards are both assigned to one or more categories together with other similar objectives or rewards. Elements that are categorized appear together in the list of reportable objectives and rewards:
Categories are defined using the categories
field in objective and reward
definitions:
# Great Balls
great_ball:
params:
- quantity
categories:
- ball
If several categories are specified, only the category listed at the top will be used to group the objectives/rewards in the selection menus.
Categories are also used to determine the icon that is displayed on the map for
research that is reported with a specific objective or reward. FreeField will
look for an icon matching the top-most category in the icon set’s pack.ini file,
and continue down the list looking for the first one that is present in pack.ini
if the top-most icon is not found. If no icons are found, FreeField falls back
to the default
icon. If there is an entry in pack.ini specifically matching
the ID of the objective or reward, that icon will be used regardless of the
categories in the definition. For more information on how icons work, please
refer to the documentation on creating icon sets.
This is the definition for the win_raid
objective:
# Win a raid
win_raid:
params:
- quantity
categories:
- raid
- battle
When FreeField attempts to load an icon for this objective, it will try the following icons in order until a matching entry in the pack.ini of the selected icon set is found:
win_raid
raid
battle
default
Adding a new category¶
You can declare a new category simply by using it in the definition of an objective or reward. When declaring a new category, ensure that you follow the snake_case naming convention of IDs in the data files.
You must declare an internationalization token in the localization files and at a minimum, localize it to English if you add a new category. Category tokens are located above the parameter tokens, and underneath the objectives and rewards in the files. If you speak other languages than English, feel free to localize the tokens to the other languages as well. The token is one of the following:
; For objectives:
category.objective.<category_id> = "Amazing objectives"
; For rewards:
category.reward.<category_id> = "Amazing rewards"
The token contains the header that is displayed for the category in the objective/reward selection boxes. You must declare this token even if you do not intend to use the category as a top-level category. Use either the “objective” or “reward” token, depending on whether you are applying the category to objectives or rewards.
Note
Make sure you declare these internationalization tokens in the same place as the tokens declared for the other categories. You can find them quickly by searching the file for “category.”