mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-12 20:53:18 +00:00
1000 lines
29 KiB
C++
1000 lines
29 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2004 - 2008 by Philippe Plantier <ayin@anathas.org>
|
|
Part of the Battle for Wesnoth Project http://www.wesnoth.org
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2
|
|
or at your option any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
//! @file builder.cpp
|
|
//! Terrain builder.
|
|
|
|
#include "global.hpp"
|
|
|
|
#include "array.hpp"
|
|
#include "builder.hpp"
|
|
#include "config.hpp"
|
|
#include "log.hpp"
|
|
#include "pathutils.hpp"
|
|
#include "terrain.hpp"
|
|
#include "util.hpp"
|
|
#include "serialization/string_utils.hpp"
|
|
|
|
#include <cassert>
|
|
#include <climits>
|
|
|
|
#define ERR_NG LOG_STREAM(err, engine)
|
|
#define DEBUG_NG LOG_STREAM(info, engine)
|
|
|
|
/** The tile width used when using basex and basey. This is not,
|
|
* necessarily, the tile width in pixels, this is totally
|
|
* arbitrary. However, it will be set to 72 for convenience.
|
|
*/
|
|
static const int TILEWIDTH = 72;
|
|
/** The position of unit graphics in a tile. Graphics whose y
|
|
* position is below this value are considered background for
|
|
* this tile; graphics whose y position is above this value are
|
|
* considered foreground.
|
|
*/
|
|
static const int UNITPOS = 36 + 18;
|
|
/** The allowed interval for the base-y position. The possible values are from
|
|
* -BASE_Y_INTERVAL to BASE_Y_INTERVAL-1
|
|
*/
|
|
static const int BASE_Y_INTERVAL = 100000;
|
|
|
|
terrain_builder::rule_image::rule_image(int layer, int x, int y, bool global_image, int cx, int cy) :
|
|
layer(layer),
|
|
basex(x),
|
|
basey(y),
|
|
variants(),
|
|
global_image(global_image),
|
|
center_x(cx),
|
|
center_y(cy)
|
|
{}
|
|
|
|
terrain_builder::tile::tile() :
|
|
flags(),
|
|
images(),
|
|
images_foreground(),
|
|
images_background(),
|
|
last_tod("invalid_tod")
|
|
{}
|
|
|
|
void terrain_builder::tile::add_image_to_cache(const std::string &tod, ordered_ri_list::const_iterator itor)
|
|
{
|
|
rule_image_variantlist::const_iterator tod_variant =
|
|
itor->second->variants.find(tod);
|
|
|
|
if(tod_variant == itor->second->variants.end())
|
|
tod_variant = itor->second->variants.find("");
|
|
|
|
if(tod_variant != itor->second->variants.end()) {
|
|
//calculate original y-value and layer from list index
|
|
int layer = itor->first / BASE_Y_INTERVAL;
|
|
int basey = itor->first % BASE_Y_INTERVAL;
|
|
|
|
if (basey < 0)
|
|
basey += BASE_Y_INTERVAL/2;
|
|
else
|
|
basey -= BASE_Y_INTERVAL/2;
|
|
|
|
if(layer < 0 || (layer == 0 && basey < UNITPOS)) {
|
|
images_background.push_back(tod_variant->second.image);
|
|
} else {
|
|
images_foreground.push_back(tod_variant->second.image);
|
|
}
|
|
}
|
|
}
|
|
|
|
void terrain_builder::tile::rebuild_cache(const std::string &tod)
|
|
{
|
|
images_background.clear();
|
|
images_foreground.clear();
|
|
|
|
ordered_ri_list::const_iterator itor;
|
|
for(itor = images.begin(); itor != images.end(); ++itor) {
|
|
add_image_to_cache(tod, itor);
|
|
}
|
|
}
|
|
|
|
void terrain_builder::tile::clear()
|
|
{
|
|
flags.clear();
|
|
images.clear();
|
|
images_foreground.clear();
|
|
images_background.clear();
|
|
last_tod = "invalid_tod";
|
|
}
|
|
|
|
void terrain_builder::tilemap::reset()
|
|
{
|
|
for(std::vector<tile>::iterator it = map_.begin(); it != map_.end(); ++it)
|
|
it->clear();
|
|
}
|
|
|
|
bool terrain_builder::tilemap::on_map(const gamemap::location &loc) const
|
|
{
|
|
if(loc.x < -2 || loc.y < -2 || loc.x > (x_ + 1) || loc.y > (y_ + 1)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
terrain_builder::tile& terrain_builder::tilemap::operator[](const gamemap::location &loc)
|
|
{
|
|
assert(on_map(loc));
|
|
|
|
return map_[(loc.x + 2) + (loc.y + 2) * (x_ + 4)];
|
|
}
|
|
|
|
const terrain_builder::tile& terrain_builder::tilemap::operator[] (const gamemap::location &loc) const
|
|
{
|
|
assert(on_map(loc));
|
|
|
|
return map_[(loc.x + 2) + (loc.y + 2) * (x_ + 4)];
|
|
}
|
|
|
|
terrain_builder::terrain_builder(const config& cfg, const config& level,
|
|
const gamemap& map, const std::string& offmap_image) :
|
|
map_(map),
|
|
tile_map_(map.w(), map.h()),
|
|
terrain_by_type_(),
|
|
building_rules_()
|
|
{
|
|
// Make sure there's nothing left in the cache,
|
|
// since it might give problems (or not?)
|
|
//image::flush_cache();
|
|
|
|
parse_config(cfg);
|
|
parse_config(level);
|
|
add_off_map_rule(offmap_image);
|
|
|
|
build_terrains();
|
|
}
|
|
|
|
const terrain_builder::imagelist *terrain_builder::get_terrain_at(const gamemap::location &loc,
|
|
const std::string &tod, const ADJACENT_TERRAIN_TYPE terrain_type)
|
|
{
|
|
if(!tile_map_.on_map(loc))
|
|
return NULL;
|
|
|
|
tile& tile_at = tile_map_[loc];
|
|
|
|
if(tod != tile_at.last_tod) {
|
|
tile_at.rebuild_cache(tod);
|
|
tile_at.last_tod = tod;
|
|
}
|
|
|
|
if(terrain_type == ADJACENT_BACKGROUND) {
|
|
if(!tile_at.images_background.empty())
|
|
return &tile_at.images_background;
|
|
}
|
|
|
|
if(terrain_type == ADJACENT_FOREGROUND) {
|
|
if(!tile_at.images_foreground.empty())
|
|
return &tile_at.images_foreground;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool terrain_builder::update_animation(const gamemap::location &loc)
|
|
{
|
|
if(!tile_map_.on_map(loc))
|
|
return false;
|
|
|
|
imagelist& bg = tile_map_[loc].images_background;
|
|
imagelist& fg = tile_map_[loc].images_foreground;
|
|
bool changed = false;
|
|
|
|
imagelist::iterator itor = bg.begin();
|
|
for(; itor != bg.end(); ++itor) {
|
|
if(itor->need_update())
|
|
changed = true;
|
|
itor->update_last_draw_time();
|
|
}
|
|
|
|
itor = fg.begin();
|
|
for(; itor != fg.end(); ++itor) {
|
|
if(itor->need_update())
|
|
changed = true;
|
|
itor->update_last_draw_time();
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
//! @todo TODO: rename this function
|
|
void terrain_builder::rebuild_terrain(const gamemap::location &loc)
|
|
{
|
|
if (tile_map_.on_map(loc)) {
|
|
tile& btile = tile_map_[loc];
|
|
// btile.images.clear();
|
|
btile.images_foreground.clear();
|
|
btile.images_background.clear();
|
|
const std::string filename =
|
|
map_.get_terrain_info(map_.get_terrain(loc)).minimap_image();
|
|
animated<image::locator> img_loc;
|
|
img_loc.add_frame(100,image::locator("terrain/" + filename + ".png"));
|
|
img_loc.start_animation(0, true);
|
|
btile.images_background.push_back(img_loc);
|
|
|
|
//Combine base and overlay image if neccessary
|
|
if(map_.get_terrain_info(map_.get_terrain(loc)).is_combined()) {
|
|
const std::string filename_ovl =
|
|
map_.get_terrain_info(map_.get_terrain(loc)).minimap_image_overlay();
|
|
animated<image::locator> img_loc_ovl;
|
|
img_loc_ovl.add_frame(100,image::locator("terrain/" + filename_ovl + ".png"));
|
|
img_loc_ovl.start_animation(0, true);
|
|
btile.images_background.push_back(img_loc_ovl);
|
|
}
|
|
}
|
|
}
|
|
|
|
void terrain_builder::rebuild_all()
|
|
{
|
|
tile_map_.reset();
|
|
terrain_by_type_.clear();
|
|
build_terrains();
|
|
}
|
|
|
|
bool terrain_builder::rule_valid(const building_rule &rule) const
|
|
{
|
|
// If the rule has no constraints, it is invalid
|
|
if(rule.constraints.empty())
|
|
return false;
|
|
|
|
// Checks if all the images referenced by the current rule are valid.
|
|
// If not, this rule will not match.
|
|
rule_imagelist::const_iterator image;
|
|
constraint_set::const_iterator constraint;
|
|
rule_image_variantlist::const_iterator variant;
|
|
|
|
for(constraint = rule.constraints.begin();
|
|
constraint != rule.constraints.end(); ++constraint) {
|
|
for(image = constraint->second.images.begin();
|
|
image != constraint->second.images.end();
|
|
++image) {
|
|
|
|
for(variant = image->variants.begin(); variant != image->variants.end(); ++variant) {
|
|
std::string s = variant->second.image_string;
|
|
s = s.substr(0, s.find_first_of(",:"));
|
|
|
|
if(!image::exists("terrain/" + s + ".png"))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool terrain_builder::start_animation(building_rule &rule)
|
|
{
|
|
rule_imagelist::iterator image;
|
|
constraint_set::iterator constraint;
|
|
rule_image_variantlist::iterator variant;
|
|
|
|
for(constraint = rule.constraints.begin();
|
|
constraint != rule.constraints.end(); ++constraint) {
|
|
|
|
for(image = constraint->second.images.begin();
|
|
image != constraint->second.images.end();
|
|
++image) {
|
|
|
|
for(variant = image->variants.begin(); variant != image->variants.end(); ++variant) {
|
|
|
|
animated<image::locator>::anim_description image_vector;
|
|
std::vector<std::string> items = utils::split(variant->second.image_string);
|
|
std::vector<std::string>::const_iterator itor = items.begin();
|
|
for(; itor != items.end(); ++itor) {
|
|
const std::vector<std::string>& items = utils::split(*itor, ':');
|
|
std::string str;
|
|
int time;
|
|
|
|
if(items.size() > 1) {
|
|
str = items.front();
|
|
time = atoi(items.back().c_str());
|
|
} else {
|
|
str = *itor;
|
|
time = 100;
|
|
}
|
|
if(image->global_image) {
|
|
image_vector.push_back(animated<image::locator>::frame_description(time,image::locator("terrain/" + str + ".png",constraint->second.loc, image->center_x, image->center_y)));
|
|
} else {
|
|
image_vector.push_back(animated<image::locator>::frame_description(time,image::locator("terrain/" + str + ".png")));
|
|
}
|
|
|
|
}
|
|
|
|
animated<image::locator> th(image_vector);
|
|
|
|
variant->second.image = th;
|
|
variant->second.image.start_animation(0, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
terrain_builder::terrain_constraint terrain_builder::rotate(const terrain_builder::terrain_constraint &constraint, int angle)
|
|
{
|
|
static const struct { int ii; int ij; int ji; int jj; } rotations[6] =
|
|
{ { 1, 0, 0, 1 }, { 1, 1, -1, 0 }, { 0, 1, -1, -1 },
|
|
{ -1, 0, 0, -1 }, { -1, -1, 1, 0 }, { 0, -1, 1, 1 } };
|
|
|
|
// The following array of matrices is intended to rotate the (x,y)
|
|
// coordinates of a point in a wesnoth hex (and wesnoth hexes are not
|
|
// regular hexes :) ).
|
|
// The base matrix for a 1-step rotation with the wesnoth tile shape
|
|
// is:
|
|
//
|
|
// r = s^-1 * t * s
|
|
//
|
|
// with s = [[ 1 0 ]
|
|
// [ 0 -sqrt(3)/2 ]]
|
|
//
|
|
// and t = [[ -1/2 sqrt(3)/2 ]
|
|
// [ -sqrt(3)/2 1/2 ]]
|
|
//
|
|
// With t being the rotation matrix (pi/3 rotation), and s a matrix
|
|
// that transforms the coordinates of the wesnoth hex to make them
|
|
// those of a regular hex.
|
|
//
|
|
// (demonstration left as an exercise for the reader)
|
|
//
|
|
// So we have
|
|
//
|
|
// r = [[ 1/2 -3/4 ]
|
|
// [ 1 1/2 ]]
|
|
//
|
|
// And the following array contains I(2), r, r^2, r^3, r^4, r^5
|
|
// (with r^3 == -I(2)), which are the successive rotations.
|
|
static const struct {
|
|
double xx;
|
|
double xy;
|
|
double yx;
|
|
double yy;
|
|
} xyrotations[6] = {
|
|
{ 1., 0., 0., 1. },
|
|
{ 1./2. , -3./4., 1., 1./2. },
|
|
{ -1./2., -3./4., 1, -1./2.},
|
|
{ -1. , 0., 0., -1. },
|
|
{ -1./2., 3./4., -1., -1./2.},
|
|
{ 1./2. , 3./4., -1., 1./2. },
|
|
};
|
|
|
|
assert(angle >= 0);
|
|
|
|
angle %= 6;
|
|
terrain_constraint ret = constraint;
|
|
|
|
// Vector i is going from n to s, vector j is going from ne to sw.
|
|
int vi = ret.loc.y - ret.loc.x/2;
|
|
int vj = ret.loc.x;
|
|
|
|
int ri = rotations[angle].ii * vi + rotations[angle].ij * vj;
|
|
int rj = rotations[angle].ji * vi + rotations[angle].jj * vj;
|
|
|
|
ret.loc.x = rj;
|
|
ret.loc.y = ri + (rj >= 0 ? rj/2 : (rj-1)/2);
|
|
|
|
for (rule_imagelist::iterator itor = ret.images.begin();
|
|
itor != ret.images.end(); ++itor) {
|
|
|
|
double vx, vy, rx, ry;
|
|
|
|
vx = double(itor->basex) - double(TILEWIDTH)/2;
|
|
vy = double(itor->basey) - double(TILEWIDTH)/2;
|
|
|
|
rx = xyrotations[angle].xx * vx + xyrotations[angle].xy * vy;
|
|
ry = xyrotations[angle].yx * vx + xyrotations[angle].yy * vy;
|
|
|
|
itor->basex = int(rx + TILEWIDTH/2);
|
|
itor->basey = int(ry + TILEWIDTH/2);
|
|
|
|
//std::cerr << "Rotation: from " << vx << ", " << vy << " to " << itor->basex <<
|
|
// ", " << itor->basey << "\n";
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void terrain_builder::replace_token(std::string &s, const std::string &token, const std::string &replacement)
|
|
{
|
|
size_t pos;
|
|
|
|
if(token.empty()) {
|
|
ERR_NG << "empty token in replace_token\n";
|
|
return;
|
|
}
|
|
while((pos = s.find(token)) != std::string::npos) {
|
|
s.replace(pos, token.size(), replacement);
|
|
}
|
|
}
|
|
|
|
void terrain_builder::replace_token(terrain_builder::rule_image &image, const std::string &token, const std::string &replacement)
|
|
{
|
|
rule_image_variantlist::iterator itor;
|
|
|
|
for(itor = image.variants.begin(); itor != image.variants.end(); ++itor) {
|
|
replace_token(itor->second, token, replacement);
|
|
}
|
|
}
|
|
|
|
void terrain_builder::replace_token(terrain_builder::rule_imagelist &list, const std::string &token, const std::string &replacement)
|
|
{
|
|
rule_imagelist::iterator itor;
|
|
|
|
for(itor = list.begin(); itor != list.end(); ++itor) {
|
|
replace_token(*itor, token, replacement);
|
|
}
|
|
}
|
|
|
|
void terrain_builder::replace_token(terrain_builder::building_rule &rule, const std::string &token, const std::string& replacement)
|
|
{
|
|
constraint_set::iterator cons;
|
|
|
|
for(cons = rule.constraints.begin(); cons != rule.constraints.end(); ++cons) {
|
|
// Transforms attributes
|
|
std::vector<std::string>::iterator flag;
|
|
|
|
for(flag = cons->second.set_flag.begin(); flag != cons->second.set_flag.end(); flag++) {
|
|
replace_token(*flag, token, replacement);
|
|
}
|
|
for(flag = cons->second.no_flag.begin(); flag != cons->second.no_flag.end(); flag++) {
|
|
replace_token(*flag, token, replacement);
|
|
}
|
|
for(flag = cons->second.has_flag.begin(); flag != cons->second.has_flag.end(); flag++) {
|
|
replace_token(*flag, token, replacement);
|
|
}
|
|
replace_token(cons->second.images, token, replacement);
|
|
}
|
|
|
|
//replace_token(rule.images, token, replacement);
|
|
}
|
|
|
|
terrain_builder::building_rule terrain_builder::rotate_rule(const terrain_builder::building_rule &rule,
|
|
int angle, const std::vector<std::string>& rot)
|
|
{
|
|
building_rule ret;
|
|
if(rot.size() != 6) {
|
|
ERR_NG << "invalid rotations\n";
|
|
return ret;
|
|
}
|
|
ret.location_constraints = rule.location_constraints;
|
|
ret.probability = rule.probability;
|
|
ret.precedence = rule.precedence;
|
|
|
|
constraint_set tmp_cons;
|
|
constraint_set::const_iterator cons;
|
|
for(cons = rule.constraints.begin(); cons != rule.constraints.end(); ++cons) {
|
|
const terrain_constraint &rcons = rotate(cons->second, angle);
|
|
|
|
tmp_cons[rcons.loc] = rcons;
|
|
}
|
|
|
|
// Normalize the rotation, so that it starts on a positive location
|
|
int minx = INT_MAX;
|
|
int miny = INT_MAX;
|
|
|
|
constraint_set::iterator cons2;
|
|
for(cons2 = tmp_cons.begin(); cons2 != tmp_cons.end(); ++cons2) {
|
|
minx = minimum<int>(cons2->second.loc.x, minx);
|
|
miny = minimum<int>(2*cons2->second.loc.y + (cons2->second.loc.x & 1), miny);
|
|
}
|
|
|
|
if((miny & 1) && (minx & 1) && (minx < 0))
|
|
miny += 2;
|
|
if(!(miny & 1) && (minx & 1) && (minx > 0))
|
|
miny -= 2;
|
|
|
|
for(cons2 = tmp_cons.begin(); cons2 != tmp_cons.end(); ++cons2) {
|
|
// Adjusts positions
|
|
cons2->second.loc += gamemap::location(-minx, -((miny-1)/2));
|
|
ret.constraints[cons2->second.loc] = cons2->second;
|
|
}
|
|
|
|
for(int i = 0; i < 6; ++i) {
|
|
int a = (angle+i) % 6;
|
|
std::string token = "@R";
|
|
push_back(token,'0' + i);
|
|
replace_token(ret, token, rot[a]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void terrain_builder::add_images_from_config(rule_imagelist& images, const config &cfg, bool global, int dx, int dy)
|
|
{
|
|
const config::child_list& cimages = cfg.get_children("image");
|
|
|
|
|
|
for(config::child_list::const_iterator img = cimages.begin(); img != cimages.end(); ++img) {
|
|
|
|
const std::string &name = (**img)["name"];
|
|
const int layer = lexical_cast_default<int>((**img)["layer"], 0);
|
|
|
|
int basex = 0, basey = 0;
|
|
if((**img)["base"].empty()) {
|
|
basex = TILEWIDTH / 2 + dx;
|
|
basey = TILEWIDTH / 2 + dy;
|
|
} else {
|
|
std::vector<std::string> base = utils::split((**img)["base"]);
|
|
|
|
if(base.size() >= 2) {
|
|
basex = atoi(base[0].c_str());
|
|
basey = atoi(base[1].c_str());
|
|
}
|
|
}
|
|
|
|
int center_x = -1, center_y = -1;
|
|
if( !(**img)["center"].empty()) {
|
|
std::vector<std::string> center = utils::split((**img)["center"]);
|
|
|
|
if(center.size() >= 2) {
|
|
center_x = atoi(center[0].c_str());
|
|
center_y = atoi(center[1].c_str());
|
|
}
|
|
}
|
|
|
|
images.push_back(rule_image(layer, basex - dx, basey - dy, global, center_x, center_y));
|
|
|
|
// Adds the main (default) variant of the image, if present
|
|
images.back().variants.insert(std::pair<std::string, rule_image_variant>("", rule_image_variant(name,"")));
|
|
|
|
// Adds the other variants of the image
|
|
const config::child_list& variants = (**img).get_children("variant");
|
|
|
|
for(config::child_list::const_iterator variant = variants.begin();
|
|
variant != variants.end(); ++variant) {
|
|
const std::string &name = (**variant)["name"];
|
|
const std::string &tod = (**variant)["tod"];
|
|
|
|
images.back().variants.insert(std::pair<std::string, rule_image_variant>(tod, rule_image_variant(name,tod)));
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void terrain_builder::add_constraints(
|
|
terrain_builder::constraint_set& constraints,
|
|
const gamemap::location& loc,
|
|
const t_translation::t_match& type, const config& global_images)
|
|
{
|
|
if(constraints.find(loc) == constraints.end()) {
|
|
// The terrain at the current location did not exist, so create it
|
|
constraints[loc] = terrain_constraint(loc);
|
|
}
|
|
|
|
if(!type.terrain.empty()) {
|
|
constraints[loc].terrain_types_match = type;
|
|
}
|
|
|
|
int x = loc.x * TILEWIDTH * 3 / 4;
|
|
int y = loc.y * TILEWIDTH + (loc.x % 2) * TILEWIDTH / 2;
|
|
add_images_from_config(constraints[loc].images, global_images, true, x, y);
|
|
}
|
|
|
|
void terrain_builder::add_constraints(terrain_builder::constraint_set &constraints,
|
|
const gamemap::location& loc, const config& cfg, const config& global_images)
|
|
|
|
{
|
|
add_constraints(constraints, loc, t_translation::t_match(cfg["type"], t_translation::WILDCARD), global_images);
|
|
|
|
terrain_constraint& constraint = constraints[loc];
|
|
|
|
std::vector<std::string> item_string = utils::split(cfg["set_flag"]);
|
|
constraint.set_flag.insert(constraint.set_flag.end(),
|
|
item_string.begin(), item_string.end());
|
|
|
|
item_string = utils::split(cfg["has_flag"]);
|
|
constraint.has_flag.insert(constraint.has_flag.end(),
|
|
item_string.begin(), item_string.end());
|
|
|
|
item_string = utils::split(cfg["no_flag"]);
|
|
constraint.no_flag.insert(constraint.no_flag.end(),
|
|
item_string.begin(), item_string.end());
|
|
|
|
add_images_from_config(constraint.images, cfg, false);
|
|
}
|
|
|
|
void terrain_builder::parse_mapstring(const std::string &mapstring,
|
|
struct building_rule &br, anchormap& anchors,
|
|
const config& global_images)
|
|
{
|
|
|
|
const t_translation::t_map map = t_translation::read_builder_map(mapstring);
|
|
|
|
// If there is an empty map leave directly.
|
|
// Determine after conversion, since a
|
|
// non-empty string can return an empty map.
|
|
if(map.empty()) {
|
|
return;
|
|
}
|
|
|
|
int lineno = (map[0][0] == t_translation::NONE_TERRAIN) ? 1 : 0;
|
|
int x = lineno;
|
|
int y = 0;
|
|
for(size_t y_off = 0; y_off < map.size(); ++y_off) {
|
|
for(size_t x_off = x; x_off < map[y_off].size(); ++x_off) {
|
|
|
|
const t_translation::t_terrain terrain = map[y_off][x_off];
|
|
|
|
if(terrain.base == t_translation::TB_DOT) {
|
|
// Dots are simple placeholders,
|
|
// which do not represent actual terrains.
|
|
} else if (terrain.overlay != 0 ) {
|
|
anchors.insert(std::pair<int, gamemap::location>(terrain.overlay, gamemap::location(x, y)));
|
|
} else if (terrain.base == t_translation::TB_STAR) {
|
|
add_constraints(br.constraints, gamemap::location(x, y), t_translation::STAR, global_images);
|
|
} else {
|
|
ERR_NG << "Invalid terrain (" << t_translation::write_terrain_code(terrain) << ") in builder map\n";
|
|
assert(false);
|
|
return;
|
|
}
|
|
x += 2;
|
|
}
|
|
|
|
if(lineno % 2 == 1) {
|
|
++y;
|
|
x = 0;
|
|
} else {
|
|
x = 1;
|
|
}
|
|
++lineno;
|
|
}
|
|
}
|
|
|
|
void terrain_builder::add_rule(building_ruleset& rules, building_rule &rule)
|
|
{
|
|
if(rule_valid(rule)) {
|
|
start_animation(rule);
|
|
rules.insert(std::pair<int, building_rule>(rule.precedence, rule));
|
|
}
|
|
}
|
|
|
|
void terrain_builder::add_rotated_rules(building_ruleset& rules, building_rule& tpl, const std::string &rotations)
|
|
{
|
|
if(rotations.empty()) {
|
|
// Adds the parsed built terrain to the list
|
|
|
|
add_rule(rules, tpl);
|
|
} else {
|
|
const std::vector<std::string>& rot = utils::split(rotations, ',');
|
|
|
|
for(size_t angle = 0; angle < rot.size(); angle++) {
|
|
building_rule rule = rotate_rule(tpl, angle, rot);
|
|
add_rule(rules, rule);
|
|
}
|
|
}
|
|
}
|
|
|
|
void terrain_builder::parse_config(const config &cfg)
|
|
{
|
|
log_scope("terrain_builder::parse_config");
|
|
|
|
// Parses the list of building rules (BRs)
|
|
const config::child_list& brs = cfg.get_children("terrain_graphics");
|
|
|
|
for(config::child_list::const_iterator br = brs.begin(); br != brs.end(); ++br) {
|
|
building_rule pbr; // Parsed Building rule
|
|
|
|
// add_images_from_config(pbr.images, **br);
|
|
|
|
if(!((**br)["x"].empty() || (**br)["y"].empty()))
|
|
pbr.location_constraints = gamemap::location(atoi((**br)["x"].c_str())-1, atoi((**br)["y"].c_str())-1);
|
|
|
|
pbr.probability = (**br)["probability"].empty() ? -1 : atoi((**br)["probability"].c_str());
|
|
pbr.precedence = (**br)["precedence"].empty() ? 0 : atoi((**br)["precedence"].c_str());
|
|
|
|
// Mapping anchor indices to anchor locations.
|
|
anchormap anchors;
|
|
|
|
// Parse the map= , if there is one (and fill the anchors list)
|
|
parse_mapstring((**br)["map"], pbr, anchors, **br);
|
|
|
|
// Parses the terrain constraints (TCs)
|
|
config::child_list tcs((*br)->get_children("tile"));
|
|
|
|
for(config::child_list::const_iterator tc = tcs.begin(); tc != tcs.end(); tc++) {
|
|
// Adds the terrain constraint to the current built terrain's list
|
|
// of terrain constraints, if it does not exist.
|
|
gamemap::location loc;
|
|
if((**tc)["x"].size()) {
|
|
loc.x = atoi((**tc)["x"].c_str());
|
|
}
|
|
if((**tc)["y"].size()) {
|
|
loc.y = atoi((**tc)["y"].c_str());
|
|
}
|
|
if(!(**tc)["loc"].empty()) {
|
|
std::vector<std::string> sloc = utils::split((**tc)["loc"]);
|
|
if(sloc.size() == 2) {
|
|
loc.x = atoi(sloc[0].c_str());
|
|
loc.y = atoi(sloc[1].c_str());
|
|
}
|
|
}
|
|
if(loc.valid()) {
|
|
add_constraints(pbr.constraints, loc, **tc, **br);
|
|
}
|
|
if((**tc)["pos"].size()) {
|
|
int pos = atoi((**tc)["pos"].c_str());
|
|
if(anchors.find(pos) == anchors.end()) {
|
|
LOG_STREAM(warn, engine) << "Invalid anchor!\n";
|
|
continue;
|
|
}
|
|
|
|
std::pair<anchormap::const_iterator, anchormap::const_iterator> range =
|
|
anchors.equal_range(pos);
|
|
|
|
for(; range.first != range.second; range.first++) {
|
|
loc = range.first->second;
|
|
add_constraints(pbr.constraints, loc, **tc, **br);
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::string global_set_flag = (**br)["set_flag"];
|
|
const std::string global_no_flag = (**br)["no_flag"];
|
|
const std::string global_has_flag = (**br)["has_flag"];
|
|
|
|
for(constraint_set::iterator constraint = pbr.constraints.begin(); constraint != pbr.constraints.end();
|
|
constraint++) {
|
|
|
|
if(global_set_flag.size())
|
|
constraint->second.set_flag.push_back(global_set_flag);
|
|
|
|
if(global_no_flag.size())
|
|
constraint->second.no_flag.push_back(global_no_flag);
|
|
|
|
if(global_has_flag.size())
|
|
constraint->second.has_flag.push_back(global_has_flag);
|
|
|
|
}
|
|
|
|
// Handles rotations
|
|
const std::string rotations = (**br)["rotations"];
|
|
|
|
add_rotated_rules(building_rules_, pbr, rotations);
|
|
|
|
}
|
|
|
|
// Debug output for the terrain rules
|
|
#if 0
|
|
std::cerr << "Built terrain rules: \n";
|
|
|
|
building_ruleset::const_iterator rule;
|
|
for(rule = building_rules_.begin(); rule != building_rules_.end(); ++rule) {
|
|
std::cerr << ">> New rule: image_background = "
|
|
<< "\n>> Location " << rule->second.location_constraints
|
|
<< "\n>> Probability " << rule->second.probability
|
|
<< "\n>> Precedence " << rule->second.precedence << '\n';
|
|
|
|
for(constraint_set::const_iterator constraint = rule->second.constraints.begin();
|
|
constraint != rule->second.constraints.end(); ++constraint) {
|
|
|
|
std::cerr << ">>>> New constraint: location = (" << constraint->second.loc
|
|
<< "), terrain types = '" << t_translation::write_list(constraint->second.terrain_types_match.terrain) << "'\n";
|
|
|
|
std::vector<std::string>::const_iterator flag;
|
|
|
|
for(flag = constraint->second.set_flag.begin(); flag != constraint->second.set_flag.end(); ++flag) {
|
|
std::cerr << ">>>>>> Set_flag: " << *flag << "\n";
|
|
}
|
|
|
|
for(flag = constraint->second.no_flag.begin(); flag != constraint->second.no_flag.end(); ++flag) {
|
|
std::cerr << ">>>>>> No_flag: " << *flag << "\n";
|
|
}
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
void terrain_builder::add_off_map_rule(const std::string& image)
|
|
{
|
|
// Build a config object
|
|
config cfg;
|
|
|
|
cfg.add_child("terrain_graphics");
|
|
config *item = cfg.child("terrain_graphics");
|
|
|
|
(*item).add_child("tile");
|
|
config *tile = (*item).child("tile");
|
|
(*tile)["x"] = "0";
|
|
(*tile)["y"] = "0";
|
|
(*tile)["type"] = t_translation::write_terrain_code(t_translation::OFF_MAP_USER);
|
|
|
|
(*tile).add_child("image");
|
|
config *tile_image = (*tile).child("image");
|
|
(*tile_image)["layer"] = "-1000";
|
|
(*tile_image)["name"] = image;
|
|
|
|
(*item)["probability"] = "100";
|
|
(*item)["no_flag"] = "base";
|
|
(*item)["set_flag"] = "base";
|
|
|
|
// Parse the object
|
|
parse_config(cfg);
|
|
}
|
|
|
|
bool terrain_builder::rule_matches(const terrain_builder::building_rule &rule,
|
|
const gamemap::location &loc, const int rule_index, const constraint_set::const_iterator type_checked) const
|
|
{
|
|
if(rule.location_constraints.valid() && rule.location_constraints != loc) {
|
|
return false;
|
|
}
|
|
|
|
if(rule.probability != -1) {
|
|
unsigned int a = (loc.x + 92872973) ^ 918273;
|
|
unsigned int b = (loc.y + 1672517) ^ 128123;
|
|
unsigned int c = (rule_index + 127390) ^ 13923787;
|
|
unsigned int abc = a*b*c + a*b + b*c + a*c + a + b + c;
|
|
unsigned int random = (abc*abc) % 100;
|
|
|
|
if(random > static_cast<unsigned int>(rule.probability)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for(constraint_set::const_iterator cons = rule.constraints.begin();
|
|
cons != rule.constraints.end(); ++cons) {
|
|
|
|
// Translated location
|
|
const gamemap::location tloc = loc + cons->second.loc;
|
|
|
|
if(!tile_map_.on_map(tloc)) {
|
|
return false;
|
|
}
|
|
|
|
//std::cout << "testing..." << builder_letter(map_.get_terrain(tloc))
|
|
|
|
// check if terrain matches except if we already know that it does
|
|
if(cons != type_checked &&
|
|
!terrain_matches(map_.get_terrain(tloc), cons->second.terrain_types_match)) {
|
|
return false;
|
|
}
|
|
|
|
const tile& btile = tile_map_[tloc];
|
|
|
|
std::vector<std::string>::const_iterator itor;
|
|
for(itor = cons->second.no_flag.begin(); itor != cons->second.no_flag.end(); ++itor) {
|
|
|
|
// If a flag listed in "no_flag" is present, the rule does not match
|
|
if(btile.flags.find(*itor) != btile.flags.end()) {
|
|
return false;
|
|
}
|
|
}
|
|
for(itor = cons->second.has_flag.begin(); itor != cons->second.has_flag.end(); ++itor) {
|
|
|
|
// If a flag listed in "has_flag" is not present, this rule does not match
|
|
if(btile.flags.find(*itor) == btile.flags.end()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void terrain_builder::apply_rule(const terrain_builder::building_rule &rule, const gamemap::location &loc)
|
|
{
|
|
for(constraint_set::const_iterator constraint = rule.constraints.begin();
|
|
constraint != rule.constraints.end(); ++constraint) {
|
|
|
|
rule_imagelist::const_iterator img;
|
|
const gamemap::location tloc = loc + constraint->second.loc;
|
|
if(!tile_map_.on_map(tloc)) {
|
|
return;
|
|
}
|
|
|
|
tile& btile = tile_map_[tloc];
|
|
|
|
// We want to order the images by layer first and base-y second,
|
|
// so we sort by layer*BASE_Y_INTERVAL + BASE_Y_INTERVAL/2 + basey
|
|
// Thus, allowed values for basey are from -50000 to 49999
|
|
for(img = constraint->second.images.begin(); img != constraint->second.images.end(); ++img) {
|
|
btile.images.insert(std::pair<int, const rule_image*>(
|
|
img->layer*BASE_Y_INTERVAL + BASE_Y_INTERVAL/2 + img->basey, &*img));
|
|
}
|
|
|
|
// Sets flags
|
|
for(std::vector<std::string>::const_iterator itor = constraint->second.set_flag.begin();
|
|
itor != constraint->second.set_flag.end(); itor++) {
|
|
btile.flags.insert(*itor);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void terrain_builder::build_terrains()
|
|
{
|
|
log_scope("terrain_builder::build_terrains");
|
|
|
|
// Builds the terrain_by_type_ cache
|
|
for(int x = -2; x <= map_.w(); ++x) {
|
|
for(int y = -2; y <= map_.h(); ++y) {
|
|
const gamemap::location loc(x,y);
|
|
const t_translation::t_terrain t = map_.get_terrain(loc);
|
|
|
|
terrain_by_type_[t].push_back(loc);
|
|
}
|
|
}
|
|
|
|
int rule_index = 0;
|
|
building_ruleset::const_iterator r;
|
|
|
|
for(r = building_rules_.begin(); r != building_rules_.end(); ++r) {
|
|
|
|
const building_rule& rule = r->second;
|
|
|
|
// Find the constraint that contains the less terrain of all terrain rules.
|
|
// We will keep a track of the matching terrains of this constraint
|
|
// and later try to apply the rule only on them
|
|
size_t min_size = INT_MAX;
|
|
t_translation::t_list min_types;
|
|
constraint_set::const_iterator min_constraint = rule.constraints.end();
|
|
|
|
for(constraint_set::const_iterator constraint = rule.constraints.begin();
|
|
constraint != rule.constraints.end(); ++constraint) {
|
|
|
|
const t_translation::t_match& match = constraint->second.terrain_types_match;
|
|
t_translation::t_list matching_types;
|
|
size_t constraint_size = 0;
|
|
|
|
for (terrain_by_type_map::iterator type_it = terrain_by_type_.begin();
|
|
type_it != terrain_by_type_.end(); type_it++) {
|
|
|
|
const t_translation::t_terrain t = type_it->first;
|
|
if (terrain_matches(t, match)) {
|
|
const size_t match_size = type_it->second.size();
|
|
constraint_size += match_size;
|
|
if (constraint_size >= min_size) {
|
|
break; // not a minimum, bail out
|
|
}
|
|
matching_types.push_back(t);
|
|
}
|
|
}
|
|
|
|
if (constraint_size < min_size) {
|
|
min_size = constraint_size;
|
|
min_types = matching_types;
|
|
min_constraint = constraint;
|
|
if (min_size == 0) {
|
|
// a constraint is never matched on this map
|
|
// we break with a empty type list
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//NOTE: if min_types is not empty, we have found a valid min_constraint;
|
|
for(t_translation::t_list::const_iterator t = min_types.begin();
|
|
t != min_types.end(); ++t) {
|
|
|
|
const std::vector<gamemap::location>* locations = &terrain_by_type_[*t];
|
|
|
|
for(std::vector<gamemap::location>::const_iterator itor = locations->begin();
|
|
itor != locations->end(); ++itor) {
|
|
const gamemap::location loc = *itor - min_constraint->second.loc;
|
|
|
|
if(rule_matches(rule, loc, rule_index, min_constraint)) {
|
|
apply_rule(rule, loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
rule_index++;
|
|
}
|
|
}
|