1 /*
2 * Copyright (C) 2002-2020 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (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, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20 #include "map_io/map_building_packet.h"
21
22 #include "base/macros.h"
23 #include "economy/request.h"
24 #include "io/fileread.h"
25 #include "io/filewrite.h"
26 #include "logic/editor_game_base.h"
27 #include "logic/map.h"
28 #include "logic/map_objects/tribes/constructionsite.h"
29 #include "logic/map_objects/tribes/tribe_descr.h"
30 #include "logic/player.h"
31 #include "map_io/map_object_loader.h"
32 #include "map_io/map_object_saver.h"
33
34 namespace Widelands {
35
36 constexpr uint16_t kCurrentPacketVersion = 3;
37
38 // constants to handle special building types
39 constexpr uint8_t kTypeBuilding = 0;
40 constexpr uint8_t kTypeConstructionSite = 1;
41 constexpr uint8_t kTypeDismantleSite = 2;
42
read(FileSystem & fs,EditorGameBase & egbase,bool const skip,MapObjectLoader & mol)43 void MapBuildingPacket::read(FileSystem& fs,
44 EditorGameBase& egbase,
45 bool const skip,
46 MapObjectLoader& mol) {
47 if (skip) {
48 return;
49 }
50 FileRead fr;
51 try {
52 fr.open(fs, "binary/building");
53 } catch (...) {
54 return;
55 }
56 try {
57 uint16_t const packet_version = fr.unsigned_16();
58 if (packet_version == kCurrentPacketVersion) {
59 const Map& map = egbase.map();
60 uint16_t const width = map.get_width();
61 uint16_t const height = map.get_height();
62 FCoords c;
63 for (c.y = 0; c.y < height; ++c.y) {
64 for (c.x = 0; c.x < width; ++c.x) {
65 if (fr.unsigned_8()) {
66 PlayerNumber const p = fr.unsigned_8();
67 Serial const serial = fr.unsigned_32();
68 char const* const name = fr.c_string();
69 uint8_t const building_type = fr.unsigned_8();
70
71 // No building lives on more than one main place.
72
73 // Get the tribe and the building index.
74 if (Player* const player = egbase.get_safe_player(p)) {
75 const TribeDescr& tribe = player->tribe();
76 const DescriptionIndex index = tribe.building_index(name);
77 const BuildingDescr* bd = tribe.get_building_descr(index);
78 // Check if tribe has this building itself
79 // OR alternatively if this building might be a conquered militarysite
80 if (!tribe.has_building(index) &&
81 !(bd && bd->type() == MapObjectType::MILITARYSITE)) {
82 throw GameDataError("tribe %s does not define building type \"%s\"",
83 tribe.name().c_str(), name);
84 }
85
86 // Now, create this Building, take extra special care for
87 // constructionsites. All data is read later.
88 Building* building;
89 if (building_type == kTypeConstructionSite) {
90 building = &egbase.warp_constructionsite(c, p, index, true);
91 } else if (building_type == kTypeDismantleSite) {
92 FormerBuildings formers = {{index, ""}};
93 building = &egbase.warp_dismantlesite(c, p, true, formers);
94 } else {
95 building = &egbase.warp_building(c, p, index);
96 }
97
98 mol.register_object<Building>(serial, *building);
99 read_priorities(*building, fr);
100 } else {
101 throw GameDataError("player %u does not exist", p);
102 }
103 }
104 }
105 }
106 } else {
107 throw UnhandledVersionError("MapBuildingPacket", packet_version, kCurrentPacketVersion);
108 }
109 } catch (const WException& e) {
110 throw GameDataError("buildings: %s", e.what());
111 }
112 }
113
114 /*
115 * Write Function
116 */
write(FileSystem & fs,EditorGameBase & egbase,MapObjectSaver & mos)117 void MapBuildingPacket::write(FileSystem& fs, EditorGameBase& egbase, MapObjectSaver& mos) {
118 FileWrite fw;
119
120 // now packet version
121 fw.unsigned_16(kCurrentPacketVersion);
122
123 // Write buildings and owner, register this with the map_object_saver so that
124 // it's data can be saved later.
125 const Map& map = egbase.map();
126 Extent const extent = map.extent();
127 iterate_Map_FCoords(map, extent, fc) {
128 upcast(Building const, building, fc.field->get_immovable());
129 if (building && building->get_position() == fc) {
130 // We only write Buildings.
131 // Buildings can life on only one main position.
132 assert(!mos.is_object_known(*building));
133
134 fw.unsigned_8(1);
135 fw.unsigned_8(building->owner().player_number());
136 fw.unsigned_32(mos.register_object(*building));
137
138 if (building->descr().type() == MapObjectType::CONSTRUCTIONSITE) {
139 upcast(PartiallyFinishedBuilding const, pfb, building);
140 fw.c_string((*pfb->building_).name().c_str());
141 fw.unsigned_8(kTypeConstructionSite);
142
143 } else if (building->descr().type() == MapObjectType::DISMANTLESITE) {
144 upcast(PartiallyFinishedBuilding const, pfb, building);
145 fw.c_string((*pfb->building_).name().c_str());
146 fw.unsigned_8(kTypeDismantleSite);
147
148 } else {
149 fw.c_string(building->descr().name().c_str());
150 fw.unsigned_8(kTypeBuilding);
151 }
152
153 write_priorities(*building, fw);
154 } else {
155 fw.unsigned_8(0);
156 }
157 }
158
159 fw.write(fs, "binary/building");
160 // DONE
161 }
162
write_priorities(const Building & building,FileWrite & fw)163 void MapBuildingPacket::write_priorities(const Building& building, FileWrite& fw) {
164 // Used to be base_priority which is no longer used. Remove after b20.
165 fw.unsigned_32(0);
166
167 std::map<int32_t, std::map<DescriptionIndex, int32_t>> type_to_priorities;
168 std::map<int32_t, std::map<DescriptionIndex, int32_t>>::iterator it;
169
170 const TribeDescr& tribe = building.owner().tribe();
171 building.collect_priorities(type_to_priorities);
172 for (it = type_to_priorities.begin(); it != type_to_priorities.end(); ++it) {
173 if (it->second.empty()) {
174 continue;
175 }
176
177 // write ware type and priority count
178 const int32_t ware_type = it->first;
179 fw.unsigned_8(ware_type);
180 fw.unsigned_8(it->second.size());
181
182 std::map<DescriptionIndex, int32_t>::iterator it2;
183 for (it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
184 std::string name;
185 DescriptionIndex const ware_index = it2->first;
186 if (wwWARE == ware_type) {
187 name = tribe.get_ware_descr(ware_index)->name();
188 } else if (wwWORKER == ware_type) {
189 name = tribe.get_worker_descr(ware_index)->name();
190 } else {
191 throw GameDataError("unrecognized ware type %d while writing priorities", ware_type);
192 }
193
194 fw.c_string(name.c_str());
195 fw.unsigned_32(it2->second);
196 }
197 }
198
199 // write 0xff so the end can be easily identified
200 fw.unsigned_8(0xff);
201 }
202
read_priorities(Building & building,FileRead & fr)203 void MapBuildingPacket::read_priorities(Building& building, FileRead& fr) {
204 fr.unsigned_32(); // unused, was base_priority which is unused. Remove after b20.
205
206 const TribeDescr& tribe = building.owner().tribe();
207 Widelands::DescriptionIndex ware_type = INVALID_INDEX;
208 // read ware type
209 while (0xff != (ware_type = fr.unsigned_8())) {
210 // read count of priorities assigned for this ware type
211 const uint8_t count = fr.unsigned_8();
212 for (uint8_t i = 0; i < count; ++i) {
213 DescriptionIndex idx;
214 if (wwWARE == ware_type) {
215 idx = tribe.safe_ware_index(fr.c_string());
216 } else if (wwWORKER == ware_type) {
217 idx = tribe.safe_worker_index(fr.c_string());
218 } else {
219 throw GameDataError("unrecognized ware type %d while reading priorities", ware_type);
220 }
221
222 building.set_priority(ware_type, idx, fr.unsigned_32());
223 }
224 }
225 }
226 } // namespace Widelands
227