1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "sprite/sprite_data.hpp"
18
19#include <algorithm>
20#include <stdexcept>
21#include <sstream>
22
23#include "util/file_system.hpp"
24#include "util/log.hpp"
25#include "util/reader_collection.hpp"
26#include "util/reader_document.hpp"
27#include "util/reader_mapping.hpp"
28#include "util/reader_object.hpp"
29#include "video/surface.hpp"
30
31SpriteData::Action::Action() :
32 name(),
33 x_offset(0),
34 y_offset(0),
35 hitbox_w(0),
36 hitbox_h(0),
37 fps(10),
38 loops(-1),
39 has_custom_loops(false),
40 surfaces()
41{
42}
43
44SpriteData::SpriteData(const ReaderMapping& mapping) :
45 actions(),
46 name()
47{
48 auto iter = mapping.get_iter();
49 while (iter.next()) {
50 if (iter.get_key() == "name") {
51 iter.get(name);
52 } else if (iter.get_key() == "action") {
53 parse_action(iter.as_mapping());
54 } else {
55 log_warning << "Unknown sprite field: " << iter.get_key() << std::endl;
56 }
57 }
58 if (actions.empty())
59 throw std::runtime_error("Error: Sprite without actions.");
60}
61
62void
63SpriteData::parse_action(const ReaderMapping& mapping)
64{
65 auto action = std::make_unique<Action>();
66
67 if (!mapping.get("name", action->name)) {
68 if (!actions.empty())
69 throw std::runtime_error(
70 "If there are more than one action, they need names!");
71 }
72
73 std::vector<float> hitbox;
74 if (mapping.get("hitbox", hitbox)) {
75 switch (hitbox.size())
76 {
77 case 4:
78 action->hitbox_h = hitbox[3];
79 action->hitbox_w = hitbox[2];
80 BOOST_FALLTHROUGH;
81 case 2:
82 action->y_offset = hitbox[1];
83 action->x_offset = hitbox[0];
84 break;
85
86 default:
87 throw std::runtime_error("hitbox should specify 2/4 coordinates");
88 }
89 }
90 mapping.get("fps", action->fps);
91 if (mapping.get("loops", action->loops))
92 {
93 action->has_custom_loops = true;
94 }
95
96 std::string mirror_action;
97 std::string clone_action;
98 if (mapping.get("mirror-action", mirror_action)) {
99 const auto act_tmp = get_action(mirror_action);
100 if (act_tmp == nullptr) {
101 std::ostringstream msg;
102 msg << "Could not mirror action. Action not found: \"" << mirror_action << "\"\n"
103 << "Mirror actions must be defined after the real one!";
104 throw std::runtime_error(msg.str());
105 } else {
106 float max_w = 0;
107 float max_h = 0;
108 for (const auto& surf : act_tmp->surfaces) {
109 auto surface = surf->clone(HORIZONTAL_FLIP);
110 max_w = std::max(max_w, static_cast<float>(surface->get_width()));
111 max_h = std::max(max_h, static_cast<float>(surface->get_height()));
112 action->surfaces.push_back(surface);
113 }
114 if (action->hitbox_w < 1) action->hitbox_w = max_w - action->x_offset;
115 if (action->hitbox_h < 1) action->hitbox_h = max_h - action->y_offset;
116 }
117 } else if (mapping.get("clone-action", clone_action)) {
118 const auto* act_tmp = get_action(clone_action);
119 if (act_tmp == nullptr) {
120 std::ostringstream msg;
121 msg << "Could not clone action. Action not found: \"" << clone_action << "\"\n"
122 << "Mirror actions must be defined after the real one!";
123 throw std::runtime_error(msg.str());
124 } else {
125 // copy everything except the name
126 const std::string oldname = action->name;
127 *action = *act_tmp;
128 action->name = oldname;
129 }
130 } else { // Load images
131 boost::optional<ReaderCollection> surfaces_collection;
132 std::vector<std::string> images;
133 if (mapping.get("images", images))
134 {
135 float max_w = 0;
136 float max_h = 0;
137 for (const auto& image : images) {
138 auto surface = Surface::from_file(FileSystem::join(mapping.get_doc().get_directory(), image));
139 max_w = std::max(max_w, static_cast<float>(surface->get_width()));
140 max_h = std::max(max_h, static_cast<float>(surface->get_height()));
141 action->surfaces.push_back(surface);
142 }
143 if (action->hitbox_w < 1) action->hitbox_w = max_w - action->x_offset;
144 if (action->hitbox_h < 1) action->hitbox_h = max_h - action->y_offset;
145 }
146 else if (mapping.get("surfaces", surfaces_collection))
147 {
148 for (const auto& i : surfaces_collection->get_objects())
149 {
150 if (i.get_name() == "surface")
151 {
152 action->surfaces.push_back(Surface::from_reader(i.get_mapping()));
153 }
154 else
155 {
156 std::stringstream msg;
157 msg << "Sprite '" << name << "' unknown tag in 'surfaces' << " << i.get_name();
158 throw std::runtime_error(msg.str());
159 }
160 }
161
162 // calculate hitbox
163 float max_w = 0;
164 float max_h = 0;
165 for (const auto& surface : action->surfaces)
166 {
167 max_w = std::max(max_w, static_cast<float>(surface->get_width()));
168 max_h = std::max(max_h, static_cast<float>(surface->get_height()));
169 }
170 if (action->hitbox_w < 1) action->hitbox_w = max_w - action->x_offset;
171 if (action->hitbox_h < 1) action->hitbox_h = max_h - action->y_offset;
172 }
173 else
174 {
175 std::stringstream msg;
176 msg << "Sprite '" << name << "' contains no images in action '"
177 << action->name << "'.";
178 throw std::runtime_error(msg.str());
179 }
180 }
181 actions[action->name] = std::move(action);
182}
183
184const SpriteData::Action*
185SpriteData::get_action(const std::string& act) const
186{
187 Actions::const_iterator i = actions.find(act);
188 if (i == actions.end()) {
189 return nullptr;
190 }
191 return i->second.get();
192}
193
194/* EOF */
195