#include <cassert>
#include <iostream>
#include <boost/bind.hpp>
#include "asserts.hpp"
#include "collision_utils.hpp"
#include "custom_object.hpp"
#include "custom_object_callable.hpp"
#include "custom_object_functions.hpp"
#include "custom_object_type.hpp"
#include "filesystem.hpp"
#include "formula_constants.hpp"
#include "json_parser.hpp"
#include "level.hpp"
#include "load_level.hpp"
#include "module.hpp"
#include "object_events.hpp"
#include "preferences.hpp"
#include "solid_map.hpp"
#include "sound.hpp"
#include "string_utils.hpp"
#include "surface_cache.hpp"
#include "unit_test.hpp"
#include "variant_callable.hpp"
#include "variant_utils.hpp"
std::map<std::string, std::string>& prototype_file_paths() {
static std::map<std::string, std::string> paths;
return paths;
}
namespace {
std::map<std::string, std::string>& object_file_paths() {
static std::map<std::string, std::string> paths;
return paths;
}
const std::string& object_file_path() {
if(preferences::load_compiled()) {
static const std::string value = "data/compiled/objects";
return value;
} else {
static const std::string value = "data/objects";
return value;
}
}
void load_file_paths() {
//find out the paths to all our files
module::get_unique_filenames_under_dir(object_file_path(), &object_file_paths());
module::get_unique_filenames_under_dir("data/object_prototypes", &::prototype_file_paths());
}
typedef std::map<std::string, const_custom_object_type_ptr> object_map;
object_map& cache() {
static object_map instance;
return instance;
}
namespace {
const std::string BaseStr = "%PROTO%";
}
variant merge_into_prototype(variant prototype_node, variant node)
{
std::map<variant, variant> result;
//mapping of animation nodes is kinda complicated: in the
//prototype there can be one specification of each animation.
//in objects there can be multiple specifications. Each
//animation in the object inherits from the specification in
//the prototype.
//
//We are going to build a completely fresh/new set of animations
//in a vector, and then wipe out all current animations and
//replace with these from the vector.
std::vector<variant> animations;
std::set<std::string> animations_seen;
foreach(variant anim, node["animation"].as_list()) {
variant id = anim["id"];
animations_seen.insert(id.as_string());
variant proto_anim;
foreach(variant candidate, prototype_node["animation"].as_list()) {
if(candidate["id"] == id) {
proto_anim = candidate;
break;
}
}
if(proto_anim.is_map()) {
//the animation is in the prototype, so we merge the
//object's definition of the animation with the
//prototype's.
animations.push_back(proto_anim + anim);
} else {
//the animation isn't in the prototype, so just add
//what is given in the object.
animations.push_back(anim);
}
}
//now go over the prototype node and add any animations that don't
//appear in the child.
foreach(variant anim, prototype_node["animation"].as_list()) {
if(animations_seen.count(anim["id"].as_string()) == 0) {
animations.push_back(anim);
}
}
foreach(variant key, prototype_node.get_keys().as_list()) {
result[key] = prototype_node[key];
}
foreach(variant key, node.get_keys().as_list()) {
variant proto_value = result[key];
variant value = node[key];
if(value.is_null()) {
//An explicit null in the object will kill the
//attribute entirely.
result[key] = variant();
continue;
}
if(key.as_string().size() > 3 && std::equal(key.as_string().begin(), key.as_string().begin() + 3, "on_")) {
if(proto_value.is_string()) {
std::string k = key.as_string();
const std::string proto_event_key = "on_" + prototype_node["id"].as_string() + "_PROTO_" + std::string(k.begin() + 3, k.end());
result[variant(proto_event_key)] = proto_value;
}
}
if(value.is_string()) {
const std::string& value_str = value.as_string();
std::string::const_iterator base_itor = std::search(value_str.begin(), value_str.end(), BaseStr.begin(), BaseStr.end());
if(base_itor != value_str.end()) {
const variant::debug_info* info = value.get_debug_info();
std::string base_value = "null";
if(proto_value.is_string()) {
base_value = proto_value.as_string();
}
const std::string s = std::string(value_str.begin(), base_itor) + base_value + std::string(base_itor + BaseStr.size(), value_str.end());
value = variant(s);
proto_value = variant();
if(info) {
value.set_debug_info(*info);
}
}
}
result[key] = append_variants(proto_value, value);
}
std::vector<variant> functions;
variant proto_fn = prototype_node["functions"];
if(proto_fn.is_string()) {
functions.push_back(proto_fn);
} else if(proto_fn.is_list()) {
foreach(variant v, proto_fn.as_list()) {
functions.push_back(v);
}
}
variant fn = node["functions"];
if(fn.is_string()) {
functions.push_back(fn);
} else if(fn.is_list()) {
foreach(variant v, fn.as_list()) {
functions.push_back(v);
}
}
if(!functions.empty()) {
result[variant("functions")] = variant(&functions);
}
result[variant("animation")] = variant(&animations);
//any objects which are explicitly merged.
result[variant("tmp")] = prototype_node["tmp"] + node["tmp"];
result[variant("vars")] = prototype_node["vars"] + node["vars"];
result[variant("consts")] = prototype_node["consts"] + node["consts"];
result[variant("variations")] = prototype_node["variations"] + node["variations"];
const variant editor_info_a = prototype_node["editor_info"];
const variant editor_info_b = node["editor_info"];
result[variant("editor_info")] = editor_info_a + editor_info_b;
if(editor_info_a.is_map() && editor_info_b.is_map() &&
editor_info_a["var"].is_list() && editor_info_b["var"].is_list()) {
std::map<variant, variant> vars_map;
std::vector<variant> items = editor_info_a["var"].as_list();
std::vector<variant> items2 = editor_info_b["var"].as_list();
items.insert(items.end(), items2.begin(), items2.end());
foreach(const variant& v, items) {
variant name = v["name"];
variant enum_value;
if(vars_map.count(name)) {
if(vars_map[name]["enum_values"].is_list() && v["enum_values"].is_list()) {
std::vector<variant> e = vars_map[name]["enum_values"].as_list();
foreach(variant item, v["enum_values"].as_list()) {
if(std::count(e.begin(), e.end(), item) == 0) {
e.push_back(item);
}
}
enum_value = variant(&e);
}
vars_map[name] = vars_map[name] + v;
if(enum_value.is_null() == false) {
vars_map[name].add_attr(variant("enum_values"), enum_value);
}
} else {
vars_map[name] = v;
}
}
std::vector<variant> v;
for(std::map<variant,variant>::const_iterator i = vars_map.begin(); i != vars_map.end(); ++i) {
v.push_back(i->second);
}
variant vars = variant(&v);
result[variant("editor_info")].add_attr(variant("var"), vars);
}
variant proto_properties = prototype_node["properties"];
variant node_properties = node["properties"];
variant properties = proto_properties + node_properties;
if(proto_properties.is_map() && node_properties.is_map()) {
foreach(const variant_pair& p, proto_properties.as_map()) {
if(node_properties.has_key(p.first)) {
const std::string proto_id = prototype_node["id"].as_string() + "_" + p.first.as_string();
properties.add_attr(variant(proto_id), p.second);
}
}
}
result[variant("properties")] = properties;
variant res(&result);
if(node.get_debug_info()) {
res.set_debug_info(*node.get_debug_info());
}
return res;
}
}
namespace customobjecttype {
void reload_file_paths() {
custom_object_type::invalidate_all_objects();
load_file_paths();
}
}
//function which finds if a node has a prototype, and if so, applies the
//prototype to the node.
variant custom_object_type::merge_prototype(variant node, std::vector<std::string>* proto_paths)
{
if(!node.has_key("prototype")) {
return node;
}
std::vector<std::string> protos = node["prototype"].as_list_string();
foreach(const std::string& proto, protos) {
//look up the object's prototype and merge it in
std::map<std::string, std::string>::const_iterator path_itor = module::find(::prototype_file_paths(), proto + ".cfg");
ASSERT_LOG(path_itor != ::prototype_file_paths().end(), "Could not find file for prototype '" << proto << "'");
variant prototype_node = json::parse_from_file(path_itor->second);
ASSERT_LOG(prototype_node["id"].as_string() == proto, "PROTOTYPE NODE FOR " << proto << " DOES NOT SPECIFY AN ACCURATE id FIELD");
if(proto_paths) {
proto_paths->push_back(path_itor->second);
}
prototype_node = merge_prototype(prototype_node, proto_paths);
node = merge_into_prototype(prototype_node, node);
}
return node;
}
const std::string* custom_object_type::get_object_path(const std::string& id)
{
if(object_file_paths().empty()) {
load_file_paths();
}
std::map<std::string, std::string>::const_iterator itor = module::find(object_file_paths(), id);
if(itor == object_file_paths().end()) {
return NULL;
}
return &itor->second;
}
const_custom_object_type_ptr custom_object_type::get(const std::string& id)
{
std::string::const_iterator dot_itor = std::find(id.begin(), id.end(), '.');
if(dot_itor != id.end()) {
const_custom_object_type_ptr parent = get(std::string(id.begin(), dot_itor));
if(!parent) {
return const_custom_object_type_ptr();
}
return parent->get_sub_object(std::string(dot_itor+1, id.end()));
}
object_map::const_iterator itor = cache().find(module::get_id(id));
if(itor != cache().end()) {
return itor->second;
}
const_custom_object_type_ptr result(create(id));
cache()[module::get_id(id)] = result;
//load the object's variations here to avoid pausing the game
//when an object starts its variation.
result->load_variations();
return result;
}
const_custom_object_type_ptr custom_object_type::get_or_die(const std::string& id)
{
const const_custom_object_type_ptr res = get(id);
ASSERT_LOG(res.get() != NULL, "UNRECOGNIZED OBJECT TYPE: '" << id << "'");
return res;
}
const_custom_object_type_ptr custom_object_type::get_sub_object(const std::string& id) const
{
std::map<std::string, const_custom_object_type_ptr>::const_iterator itor = sub_objects_.find(id);
if(itor != sub_objects_.end()) {
return itor->second;
} else {
return const_custom_object_type_ptr();
}
}
custom_object_type_ptr custom_object_type::create(const std::string& id)
{
return recreate(id, NULL);
}
namespace {
std::map<std::string, std::vector<std::string> > object_prototype_paths;
}
custom_object_type_ptr custom_object_type::recreate(const std::string& id,
const custom_object_type* old_type)
{
std::cerr << "CREATE OBJ: "<< id << "\n";
if(object_file_paths().empty()) {
load_file_paths();
}
//find the file for the object we are loading.
std::map<std::string, std::string>::const_iterator path_itor = module::find(object_file_paths(), id + ".cfg");
ASSERT_LOG(path_itor != object_file_paths().end(), "Could not find file for object '" << id << "'");
try {
std::vector<std::string> proto_paths;
variant node = merge_prototype(json::parse_from_file(path_itor->second), &proto_paths);
ASSERT_LOG(node["id"].as_string() == module::get_id(id), "IN " << path_itor->second << " OBJECT ID DOES NOT MATCH FILENAME");
//create the object
custom_object_type_ptr result(new custom_object_type(node, NULL, old_type));
object_prototype_paths[id] = proto_paths;
return result;
} catch(json::parse_error& e) {
ASSERT_LOG(false, "Error parsing WML for custom object '" << id << "' in '" << path_itor->second << "': '" << e.error_message() << "'");
} catch(graphics::load_image_error&) {
ASSERT_LOG(false, "Error loading object '" << id << "': could not load needed image");
}
// We never get here, but this stops a compiler warning.
return custom_object_type_ptr();
}
void custom_object_type::invalidate_object(const std::string& id)
{
cache().erase(module::get_id(id));
}
void custom_object_type::invalidate_all_objects()
{
cache().clear();
object_file_paths().clear();
::prototype_file_paths().clear();
}
std::vector<std::string> custom_object_type::get_all_ids()
{
std::vector<std::string> res;
std::map<std::string, std::string> file_paths;
module::get_unique_filenames_under_dir(object_file_path(), &file_paths);
for(std::map<std::string, std::string>::const_iterator i = file_paths.begin(); i != file_paths.end(); ++i) {
const std::string& fname = i->first;
if(fname.size() < 4 || std::string(fname.end()-4, fname.end()) != ".cfg") {
continue;
}
const std::string id(fname.begin(), fname.end() - 4);
res.push_back(id);
}
return res;
}
std::map<std::string,custom_object_type::EditorSummary> custom_object_type::get_editor_categories()
{
const std::string path = std::string(preferences::user_data_path()) + "/editor_cache.cfg";
variant cache, proto_cache;
if(sys::file_exists(path)) {
try {
cache = json::parse(sys::read_file(path), json::JSON_NO_PREPROCESSOR);
proto_cache = cache["prototype_info"];
} catch(...) {
}
}
std::map<std::string, bool> proto_status;
std::map<variant, variant> items, proto_info;
foreach(const std::string& id, get_all_ids()) {
variant info;
const std::string* path = get_object_path(id + ".cfg");
if(path == NULL) {
fprintf(stderr, "NO FILE FOR OBJECT '%s'\n", id.c_str());
}
const int mod_time = static_cast<int>(sys::file_mod_time(*path));
if(cache.is_map() && cache.has_key(id) && cache[id]["mod"].as_int() == mod_time) {
info = cache[id];
foreach(const std::string& p, info["prototype_paths"].as_list_string()) {
if(!proto_status.count(p)) {
const int t = static_cast<int>(sys::file_mod_time(p));
proto_info[variant(p)] = variant(t);
proto_status[p] = t == proto_cache[p].as_int();
}
if(!proto_status[p]) {
info = variant();
break;
}
}
}
if(info.is_null()) {
std::vector<std::string> proto_paths;
variant node = merge_prototype(json::parse_from_file(*path), &proto_paths);
std::map<variant,variant> summary;
summary[variant("mod")] = variant(mod_time);
std::vector<variant> proto_paths_v;
foreach(const std::string& s, proto_paths) {
proto_paths_v.push_back(variant(s));
}
summary[variant("prototype_paths")] = variant(&proto_paths_v);
if(node["animation"].is_list()) {
summary[variant("animation")] = node["animation"][0];
} else if(node["animation"].is_map()) {
summary[variant("animation")] = node["animation"];
}
if(node["editor_info"].is_map()) {
summary[variant("category")] = node["editor_info"]["category"];
if(node["editor_info"]["help"].is_string()) {
summary[variant("help")] = node["editor_info"]["help"];
}
}
info = variant(&summary);
}
items[variant(id)] = info;
}
std::map<std::string,EditorSummary> m;
for(std::map<variant,variant>::const_iterator i = items.begin(); i != items.end(); ++i) {
if(i->second.has_key("category")) {
EditorSummary& summary = m[i->first.as_string()];
summary.category = i->second["category"].as_string();
if(i->second["help"].is_string()) {
summary.help = i->second["help"].as_string();
}
summary.first_frame = i->second["animation"];
}
}
items[variant("prototype_info")] = variant(&proto_info);
const variant result = variant(&items);
sys::write_file(path, result.write_json());
return m;
}
std::vector<const_custom_object_type_ptr> custom_object_type::get_all()
{
std::vector<const_custom_object_type_ptr> res;
foreach(const std::string& id, get_all_ids()) {
res.push_back(get(id));
}
return res;
}
#ifndef NO_EDITOR
namespace {
std::set<std::string> listening_for_files, files_updated;
void on_object_file_updated(std::string path)
{
files_updated.insert(path);
}
}
int custom_object_type::reload_modified_code()
{
static int prev_nitems = 0;
const int nitems = cache().size();
if(prev_nitems == nitems && files_updated.empty()) {
return 0;
}
prev_nitems = nitems;
std::set<std::string> error_paths;
int result = 0;
for(object_map::iterator i = cache().begin(); i != cache().end(); ++i) {
const std::string* path = get_object_path(i->first + ".cfg");
if(!path) {
continue;
}
if(listening_for_files.count(*path) == 0) {
sys::notify_on_file_modification(*path, boost::bind(on_object_file_updated, *path));
listening_for_files.insert(*path);
}
if(files_updated.count(*path)) {
try {
reload_object(i->first);
++result;
} catch(...) {
error_paths.insert(*path);
}
}
}
files_updated = error_paths;
return result;
}
#endif // NO_EDITOR
void custom_object_type::set_file_contents(const std::string& file_path, const std::string& contents)
{
json::set_file_contents(file_path, contents);
for(object_map::iterator i = cache().begin(); i != cache().end(); ++i) {
const std::vector<std::string>& proto_paths = object_prototype_paths[i->first];
const std::string* path = get_object_path(i->first + ".cfg");
if(path && *path == file_path || std::count(proto_paths.begin(), proto_paths.end(), file_path)) {
std::cerr << "RELOAD OBJECT: " << i->first << " -> " << *path << "\n";
reload_object(i->first);
}
}
}
namespace {
int g_num_object_reloads = 0;
}
void custom_object_type::reload_object(const std::string& type)
{
object_map::iterator itor = cache().find(module::get_id(type));
ASSERT_LOG(itor != cache().end(), "COULD NOT RELOAD OBJECT " << type);
const_custom_object_type_ptr old_obj = itor->second;
custom_object_type_ptr new_obj;
const int begin = SDL_GetTicks();
{
const assert_recover_scope scope;
new_obj = recreate(type, old_obj.get());
std::cerr << "RELOADED OBJECT IN " << (SDL_GetTicks() - begin) << "ms\n";
}
if(!new_obj) {
return;
}
const int start = SDL_GetTicks();
foreach(custom_object* obj, custom_object::get_all(old_obj->id())) {
obj->update_type(old_obj, new_obj);
}
for(std::map<std::string, const_custom_object_type_ptr>::const_iterator i = old_obj->sub_objects_.begin(); i != old_obj->sub_objects_.end(); ++i) {
std::map<std::string, const_custom_object_type_ptr>::const_iterator j = new_obj->sub_objects_.find(i->first);
if(j != new_obj->sub_objects_.end() && i->second != j->second) {
foreach(custom_object* obj, custom_object::get_all(i->second->id())) {
obj->update_type(i->second, j->second);
}
}
}
const int end = SDL_GetTicks();
std::cerr << "UPDATED " << custom_object::get_all(old_obj->id()).size() << " OBJECTS IN " << (end - start) << "ms\n";
itor->second = new_obj;
++g_num_object_reloads;
}
int custom_object_type::num_object_reloads()
{
return g_num_object_reloads;
}
void custom_object_type::init_event_handlers(variant node,
event_handler_map& handlers,
game_logic::function_symbol_table* symbols,
const event_handler_map* base_handlers) const
{
if(symbols == NULL) {
symbols = &get_custom_object_functions_symbol_table();
}
foreach(const variant_pair& value, node.as_map()) {
const std::string& key = value.first.as_string();
if(key.size() > 3 && std::equal(key.begin(), key.begin() + 3, "on_")) {
const std::string event(key.begin() + 3, key.end());
const int event_id = get_object_event_id(event);
if(handlers.size() <= event_id) {
handlers.resize(event_id+1);
}
if(base_handlers && base_handlers->size() > event_id && (*base_handlers)[event_id] && (*base_handlers)[event_id]->str() == value.second.as_string()) {
handlers[event_id] = (*base_handlers)[event_id];
} else {
handlers[event_id] = game_logic::formula::create_optional_formula(value.second, symbols, &callable_definition_);
}
}
}
}
void init_level_definition();
custom_object_type::custom_object_type(variant node, const custom_object_type* base_type, const custom_object_type* old_type)
: id_(node["id"].as_string()),
hitpoints_(node["hitpoints"].as_int(1)),
timer_frequency_(node["timer_frequency"].as_int(-1)),
zorder_(node["zorder"].as_int()),
zsub_order_(node["zsub_order"].as_int()),
is_human_(node["is_human"].as_bool(false)),
goes_inactive_only_when_standing_(node["goes_inactive_only_when_standing"].as_bool(false)),
dies_on_inactive_(node["dies_on_inactive"].as_bool(false)),
always_active_(node["always_active"].as_bool(false)),
body_harmful_(node["body_harmful"].as_bool(true)),
body_passthrough_(node["body_passthrough"].as_bool(false)),
ignore_collide_(node["ignore_collide"].as_bool(false)),
object_level_collisions_(node["object_level_collisions"].as_bool(false)),
surface_friction_(node["surface_friction"].as_int(100)),
surface_traction_(node["surface_traction"].as_int(100)),
friction_(node["friction"].as_int()),
traction_(node["traction"].as_int(1000)),
traction_in_air_(node["traction_in_air"].as_int(0)),
traction_in_water_(node["traction_in_water"].as_int(0)),
respawns_(node["respawns"].as_bool(true)),
affected_by_currents_(node["affected_by_currents"].as_bool(false)),
is_vehicle_(node["vehicle"].as_bool(false)),
passenger_x_(node["passenger_x"].as_int()),
passenger_y_(node["passenger_y"].as_int()),
feet_width_(node["feet_width"].as_int(0)),
use_image_for_collisions_(node["use_image_for_collisions"].as_bool(false)),
static_object_(node["static_object"].as_bool(use_image_for_collisions_)),
collides_with_level_(node["collides_with_level"].as_bool(true)),
has_feet_(node["has_feet"].as_bool(true) && static_object_ == false),
adjust_feet_on_animation_change_(node["adjust_feet_on_animation_change"].as_bool(true)),
teleport_offset_x_(node["teleport_offset_x"].as_int()),
teleport_offset_y_(node["teleport_offset_y"].as_int()),
no_move_to_standing_(node["no_move_to_standing"].as_bool()),
reverse_global_vertical_zordering_(node["reverse_global_vertical_zordering"].as_bool(false)),
serializable_(node["serializable"].as_bool(true)),
solid_(solid_info::create(node)),
platform_(solid_info::create_platform(node)),
solid_platform_(node["solid_platform"].as_bool(false)),
has_solid_(solid_ || use_image_for_collisions_),
solid_dimensions_(has_solid_ || platform_ ? 0xFFFFFFFF : 0),
collide_dimensions_(0xFFFFFFFF),
weak_solid_dimensions_(has_solid_ || platform_ || node["has_platform"].as_bool(false) ? 0xFFFFFFFF : 0),
weak_collide_dimensions_(0xFFFFFFFF),
activation_border_(node["activation_border"].as_int(100)),
editor_force_standing_(node["editor_force_standing"].as_bool(false)),
hidden_in_game_(node["hidden_in_game"].as_bool(false)),
platform_offsets_(node["platform_offsets"].as_list_int_optional()),
slot_properties_base_(-1),
use_absolute_screen_coordinates_(node["use_absolute_screen_coordinates"].as_bool(false))
{
custom_object_callable::instance();
init_level_definition();
#ifndef NO_EDITOR
if(node.has_key("editor_info")) {
editor_info_.reset(new editor_entity_info(node["editor_info"]));
}
#endif // !NO_EDITOR
if(node.has_key("preload_sounds")) {
//Pre-load any sounds that should be present when we create
//this object type.
foreach(std::string sound, util::split(node["preload_sounds"].as_string())) {
sound::preload(sound);
}
}
const bool is_variation = base_type != NULL;
//make it so any formula has these constants defined.
const game_logic::constants_loader scope_consts(node["consts"]);
//if some constants change from base to variation, then we have to
//re-parse all formulas.
if(scope_consts.same_as_base() == false) {
base_type = NULL;
}
if(node.has_key("solid_dimensions")) {
weak_solid_dimensions_ = solid_dimensions_ = 0;
foreach(std::string key, node["solid_dimensions"].as_list_string()) {
if(key.empty() == false && key[0] == '~') {
key = std::string(key.begin()+1, key.end());
weak_solid_dimensions_ |= (1 << get_solid_dimension_id(key));
} else {
solid_dimensions_ |= (1 << get_solid_dimension_id(key));
}
}
weak_solid_dimensions_ |= solid_dimensions_;
}
if(node.has_key("collide_dimensions")) {
weak_collide_dimensions_ = collide_dimensions_ = 0;
foreach(std::string key, node["collide_dimensions"].as_list_string()) {
if(key.empty() == false && key[0] == '~') {
key = std::string(key.begin()+1, key.end());
weak_collide_dimensions_ |= (1 << get_solid_dimension_id(key));
} else {
collide_dimensions_ |= (1 << get_solid_dimension_id(key));
}
}
weak_collide_dimensions_ |= collide_dimensions_;
}
foreach(variant anim, node["animation"].as_list()) {
boost::shared_ptr<frame> f;
try {
f.reset(new frame(anim));
} catch(frame::error&) {
ASSERT_LOG(false, "ERROR LOADING FRAME IN OBJECT '" << id_ << "'");
}
if(use_image_for_collisions_) {
f->set_image_as_solid();
}
if(f->solid()) {
has_solid_ = true;
}
frames_[anim["id"].as_string()].push_back(f);
const int duplicates = anim["duplicates"].as_int();
if(duplicates > 1) {
for(int n = 1; n != duplicates; ++n) {
frames_[anim["id"].as_string()].push_back(f);
}
}
if(!default_frame_) {
default_frame_ = f;
}
}
ASSERT_LOG(default_frame_, "OBJECT " << id_ << " NO ANIMATIONS FOR OBJECT: " << node.write_json() << "'");
std::vector<variant> available_frames;
for(frame_map::const_iterator i = frames_.begin(); i != frames_.end(); ++i) {
available_frames.push_back(variant(i->first));
}
available_frames_ = variant(&available_frames);
mass_ = node["mass"].as_int(default_frame_->collide_w() * default_frame_->collide_h());
foreach(variant child, node["child"].as_list()) {
const std::string& child_id = child["child_id"].as_string();
children_[child_id] = child;
}
assert(default_frame_);
next_animation_formula_ = game_logic::formula::create_optional_formula(node["next_animation"], function_symbols());
foreach(variant particle_node, node["particle_system"].as_list()) {
particle_factories_[particle_node["id"].as_string()] = particle_system_factory::create_factory(particle_node);
}
if(!is_variation) {
foreach(variant object_node, node["object_type"].as_list()) {
variant merged = merge_prototype(object_node);
std::string sub_key = object_node["id"].as_string();
if(old_type && old_type->sub_objects_.count(sub_key) &&
old_type->sub_objects_.find(sub_key)->second->node_ == merged) {
//We are recreating this object, and the sub object node
//hasn't changed at all, so just reuse the same sub object.
sub_objects_[sub_key] = old_type->sub_objects_.find(sub_key)->second;
} else {
custom_object_type* type = new custom_object_type(merged);
type->id_ = id_ + "." + type->id_;
if(old_type && type->node_.is_null()){
type->node_ = merged;
}
//std::cerr << "MERGED PROTOTYPE FOR " << type->id_ << ": " << merged.write_json() << "\n";
sub_objects_[sub_key].reset(type);
}
}
}
if(node.has_key("parallax_scale_x") || node.has_key("parallax_scale_y")) {
parallax_scale_millis_.reset(new std::pair<int, int>(node["parallax_scale_x"].as_int(1000), node["parallax_scale_y"].as_int(1000)));
}
variant vars = node["vars"];
if(vars.is_null() == false) {
std::vector<std::string> var_str;
foreach(variant key, vars.get_keys().as_list()) {
variables_[key.as_string()] = vars[key];
var_str.push_back(key.as_string());
}
if(!var_str. empty()) {
game_logic::formula_callable_definition::entry* entry = callable_definition_.get_entry(CUSTOM_OBJECT_VARS);
ASSERT_LOG(entry != NULL, "CANNOT FIND VARS ENTRY IN OBJECT");
entry->type_definition_holder = game_logic::create_formula_callable_definition(&var_str[0], &var_str[0] + var_str.size());
entry->type_definition = entry->type_definition_holder.get();
}
}
variant tmp_vars = node["tmp"];
if(tmp_vars.is_null() == false) {
std::vector<std::string> var_str;
foreach(variant key, tmp_vars.get_keys().as_list()) {
tmp_variables_[key.as_string()] = tmp_vars[key];
var_str.push_back(key.as_string());
}
if(!var_str.empty()) {
game_logic::formula_callable_definition::entry* entry = callable_definition_.get_entry(CUSTOM_OBJECT_TMP);
ASSERT_LOG(entry != NULL, "CANNOT FIND TMP ENTRY IN OBJECT");
entry->type_definition_holder = game_logic::create_formula_callable_definition(&var_str[0], &var_str[0] + var_str.size());
entry->type_definition = entry->type_definition_holder.get();
}
}
//std::cerr << "TMP_VARIABLES: '" << id_ << "' -> " << tmp_variables_.size() << "\n";
consts_.reset(new game_logic::map_formula_callable);
variant consts = node["consts"];
if(consts.is_null() == false) {
foreach(variant key, consts.get_keys().as_list()) {
consts_->add(key.as_string(), consts[key]);
}
}
if(node.has_key("tags")) {
const std::vector<std::string> tags = util::split(node["tags"].as_string());
foreach(const std::string& tag, tags) {
tags_[tag] = variant(1);
}
}
slot_properties_base_ = callable_definition_.num_slots();
foreach(variant properties_node, node["properties"].as_list()) {
foreach(variant key, properties_node.get_keys().as_list()) {
const std::string& k = key.as_string();
callable_definition_.add_property(k);
variant value = properties_node[key];
property_entry& entry = properties_[k];
if(value.is_string()) {
entry.getter = game_logic::formula::create_optional_formula(value, function_symbols(), &callable_definition_);
} else if(value.is_map()) {
entry.getter = game_logic::formula::create_optional_formula(value["get"], function_symbols(), &callable_definition_);
entry.setter = game_logic::formula::create_optional_formula(value["set"], function_symbols(), &callable_definition_);
} else {
entry.const_value.reset(new variant(value));
}
if(entry.getter) {
variant v;
if(entry.getter->evaluates_to_constant(v)) {
entry.getter.reset();
entry.const_value.reset(new variant(v));
}
}
slot_properties_.push_back(entry);
}
}
variant variations = node["variations"];
if(variations.is_null() == false) {
foreach(const variant_pair& v, variations.as_map()) {
variations_[v.first.as_string()] = game_logic::formula::create_optional_formula(v.second, &get_custom_object_functions_symbol_table());
}
node_ = node;
}
game_logic::register_formula_callable_definition("object_type", &callable_definition_);
#if defined(USE_GLES2)
if(node.has_key("shader")) {
shader_.reset(new gles2::shader_program(node["shader"]));
}
if(node.has_key("effects")) {
effects_.clear();
for(size_t n = 0; n < node["effects"].num_elements(); ++n) {
effects_.push_back(gles2::shader_ptr(new gles2::shader_program(node["effects"][n])));
}
}
#endif
if(base_type) {
//if we're a variation, just get the functions from our base type.
//variations can't define new functions.
object_functions_ = base_type->object_functions_;
} else if(node.has_key("functions")) {
object_functions_.reset(new game_logic::function_symbol_table);
object_functions_->set_backup(&get_custom_object_functions_symbol_table());
const variant fn = node["functions"];
if(fn.is_string()) {
game_logic::formula f(fn, object_functions_.get());
} else if(fn.is_list()) {
for(int n = 0; n != fn.num_elements(); ++n) {
game_logic::formula f(fn[n], object_functions_.get());
}
}
}
init_event_handlers(node, event_handlers_, function_symbols(), base_type ? &base_type->event_handlers_ : NULL);
}
custom_object_type::~custom_object_type()
{
}
const frame& custom_object_type::default_frame() const
{
return *default_frame_;
}
const frame& custom_object_type::get_frame(const std::string& key) const
{
frame_map::const_iterator itor = frames_.find(key);
if(itor == frames_.end() || itor->second.empty()) {
ASSERT_LOG(key == "normal", "UNKNOWN FRAME " << key << " IN " << id_);
return default_frame();
} else {
if(itor->second.size() == 1) {
return *itor->second.front().get();
} else {
return *itor->second[rand()%itor->second.size()].get();
}
}
}
game_logic::const_formula_ptr custom_object_type::get_event_handler(int event) const
{
if(event >= event_handlers_.size()) {
return game_logic::const_formula_ptr();
} else {
return event_handlers_[event];
}
}
const_particle_system_factory_ptr custom_object_type::get_particle_system_factory(const std::string& id) const
{
std::map<std::string, const_particle_system_factory_ptr>::const_iterator i = particle_factories_.find(id);
ASSERT_LOG(i != particle_factories_.end(), "Unknown particle system type in " << id_ << ": " << id);
return i->second;
}
game_logic::function_symbol_table* custom_object_type::function_symbols() const
{
if(object_functions_) {
return object_functions_.get();
} else {
return &get_custom_object_functions_symbol_table();
}
}
namespace {
void execute_variation_command(variant cmd, game_logic::formula_callable& obj)
{
if(cmd.is_list()) {
foreach(variant c, cmd.as_list()) {
execute_variation_command(c, obj);
}
} else if(cmd.try_convert<game_logic::command_callable>()) {
cmd.try_convert<game_logic::command_callable>()->execute(obj);
}
}
}
const_custom_object_type_ptr custom_object_type::get_variation(const std::vector<std::string>& variations) const
{
ASSERT_LOG(node_.is_null() == false, "tried to set variation in object " << id_ << " which has no variations");
const_custom_object_type_ptr& result = variations_cache_[variations];
if(!result) {
variant node = node_;
boost::intrusive_ptr<game_logic::map_formula_callable> callable(new game_logic::map_formula_callable);
callable->add("doc", variant(variant_callable::create(&node)));
foreach(const std::string& v, variations) {
std::map<std::string, game_logic::const_formula_ptr>::const_iterator var_itor = variations_.find(v);
ASSERT_LOG(var_itor != variations_.end(), "COULD NOT FIND VARIATION " << v << " IN " << id_);
variant cmd = var_itor->second->execute(*callable);
execute_variation_command(cmd, *callable);
//std::cerr << "VARIATION " << v << ":\n--- BEFORE ---\n" << node.write_json() << "\n--- AFTER ---\n" << node_.write_json() << "\n--- DONE ---\n";
}
//set our constants so the variation can decide whether it needs
//to re-parse formulas or not.
const game_logic::constants_loader scope_consts(node_["consts"]);
//copy the id over from the parent object, to make sure it's
//the same. This is important for nested objects.
custom_object_type* obj_type = new custom_object_type(node, this);
obj_type->id_ = id_;
result.reset(obj_type);
}
return result;
}
void custom_object_type::load_variations() const
{
if(node_.is_null() || variations_.empty() || !node_.has_key("load_variations")) {
return;
}
const std::vector<std::string> variations_to_load = util::split(node_["load_variations"].as_string());
foreach(const std::string& v, variations_to_load) {
get_variation(std::vector<std::string>(1, v));
}
}
#include "texture.hpp"
#include "surface_cache.hpp"
BENCHMARK(custom_object_type_load)
{
static std::map<std::string,std::string> file_paths;
if(file_paths.empty()) {
module::get_unique_filenames_under_dir("data/objects", &file_paths);
}
BENCHMARK_LOOP {
for(std::map<std::string,std::string>::const_iterator i = file_paths.begin(); i != file_paths.end(); ++i) {
if(i->first.size() > 4 && std::equal(i->first.end()-4, i->first.end(), ".cfg")) {
custom_object_type::create(std::string(i->first.begin(), i->first.end()-4));
}
}
graphics::surface_cache::clear();
graphics::texture::clear_textures();
}
}
BENCHMARK(custom_object_type_frogatto_load)
{
BENCHMARK_LOOP {
custom_object_type::create("frogatto_playable");
graphics::texture::clear_textures();
graphics::surface_cache::clear();
}
}
UTILITY(object_definition)
{
foreach(const std::string& arg, args) {
const_custom_object_type_ptr obj = custom_object_type::get(arg);
ASSERT_LOG(obj.get() != NULL, "NO OBJECT FOUND: " << arg);
const std::string* fname = custom_object_type::get_object_path(arg + ".cfg");
ASSERT_LOG(fname != NULL, "NO OBJECT FILE FOUND: " << arg);
const variant node = custom_object_type::merge_prototype(json::parse_from_file(*fname));
std::cout << "OBJECT " << arg << "\n---\n" << node.write_json(true) << "\n---\n";
}
}
UTILITY(test_all_objects)
{
custom_object_type::get_all();
}