1 /**
2  * @file
3  * @brief Base building related stuff.
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 */
24 
25 #include "cp_building.h"
26 #include "../../cl_shared.h"
27 #include "../../../shared/parse.h"
28 #include "cp_campaign.h"
29 #include "cp_time.h"
30 
31 /**
32  * @brief Returns if a building is fully buildt up
33  * @param[in] building Pointer to the building to check
34  * @note it always return @c true for buildings with {0, 0} timeStart
35  */
B_IsBuildingBuiltUp(const building_t * building)36 bool B_IsBuildingBuiltUp (const building_t* building)
37 {
38 	if (!building)
39 		return false;
40 	if (building->timeStart.day == 0 && building->timeStart.sec == 0)
41 		return true;
42 	date_t due = building->timeStart;
43 	due.day += building->buildTime;
44 	return Date_IsDue(&due);
45 }
46 
47 /**
48  * @brief Returns the time remaining time of a building construction
49  * @param[in] building Pointer to the building to check
50  */
B_GetConstructionTimeRemain(const building_t * building)51 float B_GetConstructionTimeRemain (const building_t* building)
52 {
53 	date_t diff = Date_Substract(building->timeStart, ccs.date);
54 	diff.day += building->buildTime;
55 	return diff.day + (float)diff.sec / SECONDS_PER_DAY;
56 }
57 
58 static const struct buildingTypeMapping_s {
59 	const char* id;
60 	buildingType_t type;
61 } buildingTypeMapping[] = {
62 	{ "lab", B_LAB },
63 	{ "hospital", B_HOSPITAL },
64 	{ "aliencont", B_ALIEN_CONTAINMENT },
65 	{ "workshop",B_WORKSHOP },
66 	{ "storage", B_STORAGE },
67 	{ "hangar", B_HANGAR },
68 	{ "smallhangar",B_SMALL_HANGAR },
69 	{ "quarters", B_QUARTERS },
70 	{ "power", B_POWER },
71 	{ "command", B_COMMAND },
72 	{ "amstorage", B_ANTIMATTER },
73 	{ "entrance", B_ENTRANCE },
74 	{ "missile", B_DEFENCE_MISSILE },
75 	{ "laser", B_DEFENCE_LASER },
76 	{ "radar", B_RADAR },
77 	{ NULL, MAX_BUILDING_TYPE }
78 };
79 
80 /**
81  * @brief Returns the building type for a given building identified by its building id
82  * from the ufo script files
83  * @sa B_ParseBuildings
84  * @param[in] buildingID The script building id that should get converted into the enum value
85  */
B_GetBuildingTypeByBuildingID(const char * buildingID)86 buildingType_t B_GetBuildingTypeByBuildingID (const char* buildingID)
87 {
88 	for (const struct buildingTypeMapping_s* v = buildingTypeMapping; v->id; v++)
89 		if (Q_streq(buildingID, v->id))
90 			return v->type;
91 	return MAX_BUILDING_TYPE;
92 }
93 
94 /**
95  * @brief Holds the names of valid entries in the basemanagement.ufo file.
96  *
97  * The valid definition names for BUILDINGS (building_t) in the basemanagement.ufo file.
98  * to the appropriate values in the corresponding struct
99  */
100 static const value_t valid_building_vars[] = {
101 	{"map_name", V_HUNK_STRING, offsetof(building_t, mapPart), 0},	/**< Name of the map file for generating basemap. */
102 	{"max_count", V_INT, offsetof(building_t, maxCount), MEMBER_SIZEOF(building_t, maxCount)},	/**< How many building of the same type allowed? */
103 	{"level", V_FLOAT, offsetof(building_t, level), MEMBER_SIZEOF(building_t, level)},	/**< building level */
104 	{"name", V_TRANSLATION_STRING, offsetof(building_t, name), 0},	/**< The displayed building name. */
105 	{"tech", V_HUNK_STRING, offsetof(building_t, pedia), 0},	/**< The pedia-id string for the associated pedia entry. */
106 	{"status", V_INT, offsetof(building_t, buildingStatus), MEMBER_SIZEOF(building_t, buildingStatus)},	/**< The current status of the building. */
107 	{"image", V_HUNK_STRING, offsetof(building_t, image), 0},	/**< Identifies the image for the building. */
108 	{"size", V_POS, offsetof(building_t, size), MEMBER_SIZEOF(building_t, size)},	/**< Building size. */
109 	{"fixcosts", V_INT, offsetof(building_t, fixCosts), MEMBER_SIZEOF(building_t, fixCosts)},	/**< Cost to build. */
110 	{"varcosts", V_INT, offsetof(building_t, varCosts), MEMBER_SIZEOF(building_t, varCosts)},	/**< Costs that will come up by using the building. */
111 	{"build_time", V_INT, offsetof(building_t, buildTime), MEMBER_SIZEOF(building_t, buildTime)},	/**< How many days it takes to construct the building. */
112 	{"starting_employees", V_INT, offsetof(building_t, maxEmployees), MEMBER_SIZEOF(building_t, maxEmployees)},	/**< How many employees to hire on construction in the first base. */
113 	{"capacity", V_INT, offsetof(building_t, capacity), MEMBER_SIZEOF(building_t, capacity)},	/**< A size value that is used by many buildings in a different way. */
114 
115 	/*event handler functions */
116 	{"onconstruct", V_HUNK_STRING, offsetof(building_t, onConstruct), 0}, /**< Event handler. */
117 	{"ondestroy", V_HUNK_STRING, offsetof(building_t, onDestroy), 0}, /**< Event handler. */
118 	{"onenable", V_HUNK_STRING, offsetof(building_t, onEnable), 0}, /**< Event handler. */
119 	{"ondisable", V_HUNK_STRING, offsetof(building_t, onDisable), 0}, /**< Event handler. */
120 	{"mandatory", V_BOOL, offsetof(building_t, mandatory), MEMBER_SIZEOF(building_t, mandatory)}, /**< Automatically construct this building when a base is set up. Must also set the pos-flag. */
121 	{nullptr, V_NULL, 0, 0}
122 };
123 
124 /**
125  * @brief Copies an entry from the building description file into the list of building types.
126  * @note Parses one "building" entry in the basemanagement.ufo file and writes
127  * it into the next free entry in bmBuildings[0], which is the list of buildings
128  * in the first base (building_t).
129  * @param[in] name Unique script id of a building. This is parsed from "building xxx" -> id=xxx.
130  * @param[in] text the whole following text that is part of the "building" item definition in .ufo.
131  * @param[in] link Bool value that decides whether to link the tech pointer in or not
132  * @sa CL_ParseScriptFirst (link is false here)
133  * @sa CL_ParseScriptSecond (link it true here)
134  */
B_ParseBuildings(const char * name,const char ** text,bool link)135 void B_ParseBuildings (const char* name, const char** text, bool link)
136 {
137 	building_t* building;
138 	technology_t* techLink;
139 	const char* errhead = "B_ParseBuildings: unexpected end of file (names ";
140 	const char* token;
141 
142 	/* get id list body */
143 	token = Com_Parse(text);
144 	if (!*text || *token != '{') {
145 		Com_Printf("B_ParseBuildings: building \"%s\" without body ignored\n", name);
146 		return;
147 	}
148 
149 	if (ccs.numBuildingTemplates >= MAX_BUILDINGS)
150 		cgi->Com_Error(ERR_DROP, "B_ParseBuildings: too many buildings");
151 
152 	if (!link) {
153 		int i;
154 		for (i = 0; i < ccs.numBuildingTemplates; i++) {
155 			if (Q_streq(ccs.buildingTemplates[i].id, name)) {
156 				Com_Printf("B_ParseBuildings: Second building with same name found (%s) - second ignored\n", name);
157 				return;
158 			}
159 		}
160 
161 		/* new entry */
162 		building = &ccs.buildingTemplates[ccs.numBuildingTemplates];
163 		OBJZERO(*building);
164 		building->id = Mem_PoolStrDup(name, cp_campaignPool, 0);
165 
166 		Com_DPrintf(DEBUG_CLIENT, "...found building %s\n", building->id);
167 
168 		/* set standard values */
169 		building->tpl = building;	/* Self-link just in case ... this way we can check if it is a template or not. */
170 		building->idx = -1;			/* No entry in buildings list (yet). */
171 		building->base = nullptr;
172 		building->buildingType = MAX_BUILDING_TYPE;
173 		building->dependsBuilding = nullptr;
174 		building->maxCount = -1;	/* Default: no limit */
175 		building->size[0] = 1;
176 		building->size[1] = 1;
177 
178 		ccs.numBuildingTemplates++;
179 		do {
180 			/* get the name type */
181 			token = cgi->Com_EParse(text, errhead, name);
182 			if (!*text)
183 				break;
184 			if (*token == '}')
185 				break;
186 
187 			/* get values */
188 			if (Q_streq(token, "type")) {
189 				token = cgi->Com_EParse(text, errhead, name);
190 				if (!*text)
191 					return;
192 
193 				building->buildingType = B_GetBuildingTypeByBuildingID(token);
194 				if (building->buildingType >= MAX_BUILDING_TYPE)
195 					Com_Printf("didn't find buildingType '%s'\n", token);
196 			} else {
197 				/* no linking yet */
198 				if (Q_streq(token, "depends")) {
199 					cgi->Com_EParse(text, errhead, name);
200 					if (!*text)
201 						return;
202 				} else {
203 					if (!Com_ParseBlockToken(name, text, building, valid_building_vars, cp_campaignPool, token))
204 						Com_Printf("B_ParseBuildings: unknown token \"%s\" ignored (building %s)\n", token, name);
205 				}
206 			}
207 		} while (*text);
208 		if (building->size[0] < 1 || building->size[1] < 1 || building->size[0] >= BASE_SIZE || building->size[1] >= BASE_SIZE) {
209 			Com_Printf("B_ParseBuildings: Invalid size for building %s (%i, %i)\n", building->id, (int)building->size[0], (int)building->size[1]);
210 			ccs.numBuildingTemplates--;
211 		}
212 	} else {
213 		building = B_GetBuildingTemplate(name);
214 		if (!building)
215 			cgi->Com_Error(ERR_DROP, "B_ParseBuildings: Could not find building with id %s\n", name);
216 
217 		techLink = RS_GetTechByProvided(name);
218 		if (techLink)
219 			building->tech = techLink;
220 
221 		do {
222 			/* get the name type */
223 			token = cgi->Com_EParse(text, errhead, name);
224 			if (!*text)
225 				break;
226 			if (*token == '}')
227 				break;
228 			/* get values */
229 			if (Q_streq(token, "depends")) {
230 				const building_t* dependsBuilding = B_GetBuildingTemplate(cgi->Com_EParse(text, errhead, name));
231 				if (!dependsBuilding)
232 					cgi->Com_Error(ERR_DROP, "Could not find building depend of %s\n", building->id);
233 				building->dependsBuilding = dependsBuilding;
234 				if (!*text)
235 					return;
236 			}
237 		} while (*text);
238 	}
239 }
240 
241 /**
242  * @brief Checks the parsed buildings for errors
243  * @return false if there are errors - true otherwise
244  */
B_BuildingScriptSanityCheck(void)245 bool B_BuildingScriptSanityCheck (void)
246 {
247 	int i, error = 0;
248 	building_t* b;
249 
250 	for (i = 0, b = ccs.buildingTemplates; i < ccs.numBuildingTemplates; i++, b++) {
251 		if (!b->name) {
252 			error++;
253 			Com_Printf("...... no name for building '%s' given\n", b->id);
254 		}
255 		if (!b->image) {
256 			error++;
257 			Com_Printf("...... no image for building '%s' given\n", b->id);
258 		}
259 		if (!b->pedia) {
260 			error++;
261 			Com_Printf("...... no pedia link for building '%s' given\n", b->id);
262 		} else if (!RS_GetTechByID(b->pedia)) {
263 			error++;
264 			Com_Printf("...... could not get pedia entry tech (%s) for building '%s'\n", b->pedia, b->id);
265 		}
266 	}
267 
268 	return !error;
269 }
270 
271 /**
272  * @brief Returns the building in the global building-types list that has the unique name buildingID.
273  * @param[in] buildingName The unique id of the building (building_t->id).
274  * @return Building template pointer if found, nullptr otherwise
275  * @todo make the returned pointer const
276  */
B_GetBuildingTemplateSilent(const char * buildingName)277 building_t* B_GetBuildingTemplateSilent (const char* buildingName)
278 {
279 	int i = 0;
280 
281 	if (!buildingName)
282 		return nullptr;
283 	for (i = 0; i < ccs.numBuildingTemplates; i++) {
284 		building_t* buildingTemplate = &ccs.buildingTemplates[i];
285 		if (Q_streq(buildingTemplate->id, buildingName))
286 			return buildingTemplate;
287 	}
288 	return nullptr;
289 }
290 
291 /**
292  * @brief Returns the building in the global building-types list that has the unique name buildingID.
293  * @param[in] buildingName The unique id of the building (building_t->id).
294  * @return Building template pointer if found, nullptr otherwise
295  * @todo make the returned pointer const
296  */
B_GetBuildingTemplate(const char * buildingName)297 building_t* B_GetBuildingTemplate (const char* buildingName)
298 {
299 	if (!buildingName || buildingName[0] == '\0') {
300 		Com_Printf("No, or empty building ID\n");
301 		return nullptr;
302 	}
303 
304 	building_t* buildingTemplate = B_GetBuildingTemplateSilent(buildingName);
305 	if (!buildingTemplate)
306 		Com_Printf("Building %s not found\n", buildingName);
307 	return buildingTemplate;
308 }
309 
310 /**
311  * @brief Returns the building template in the global building-types list for a buildingType.
312  * @param[in] type Building type.
313  */
B_GetBuildingTemplateByType(buildingType_t type)314 const building_t* B_GetBuildingTemplateByType(buildingType_t type)
315 {
316 	for (int i = 0; i < ccs.numBuildingTemplates; i++) {
317 		const building_t* buildingTemplate = &ccs.buildingTemplates[i];
318 		if (buildingTemplate->buildingType == type)
319 			return buildingTemplate;
320 	}
321 	return nullptr;
322 }
323 
324 /**
325  * @brief Check that the dependences of a building is operationnal
326  * @param[in] building Pointer to the building to check
327  * @return true if base contains needed dependence for entering building
328  */
B_CheckBuildingDependencesStatus(const building_t * building)329 bool B_CheckBuildingDependencesStatus (const building_t* building)
330 {
331 	assert(building);
332 
333 	if (!building->dependsBuilding)
334 		return true;
335 
336 	/* Make sure the dependsBuilding pointer is really a template .. just in case. */
337 	assert(building->dependsBuilding == building->dependsBuilding->tpl);
338 
339 	return B_GetBuildingStatus(building->base, building->dependsBuilding->buildingType);
340 }
341 
342 /**
343  * @brief Run eventhandler script for a building
344  * @param[in] buildingTemplate Building type (template) to run event for
345  * @param[in] base The base to run it at
346  * @param[in] eventType Type of the event to run
347  * @return @c true if an event was fired @c false otherwise (the building may not have one)
348  */
B_FireEvent(const building_t * buildingTemplate,const base_t * base,buildingEvent_t eventType)349 bool B_FireEvent (const building_t* buildingTemplate, const base_t* base, buildingEvent_t eventType)
350 {
351 	const char* command = nullptr;
352 
353 	assert(buildingTemplate);
354 	assert(base);
355 
356 	switch (eventType) {
357 		case B_ONCONSTRUCT:
358 			command = buildingTemplate->onConstruct;
359 			break;
360 		case B_ONENABLE:
361 			command = buildingTemplate->onEnable;
362 			break;
363 		case B_ONDISABLE:
364 			command = buildingTemplate->onDisable;
365 			break;
366 		case B_ONDESTROY:
367 			command = buildingTemplate->onDestroy;
368 			break;
369 		default:
370 			cgi->Com_Error(ERR_DROP, "B_FireEvent: Invalid Event\n");
371 	}
372 
373 	if (Q_strvalid(command)) {
374 		cgi->Cmd_ExecuteString("%s %i %i", command, base->idx, buildingTemplate->buildingType);
375 		return true;
376 	}
377 
378 	return false;
379 }
380