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