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 | |
31 | SpriteData::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 | |
44 | SpriteData::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 | |
62 | void |
63 | SpriteData::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 | |
184 | const SpriteData::Action* |
185 | SpriteData::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 | |