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