Spellforce-Spell-framework
Loading...
Searching...
No Matches
sf_building_loader.c
Go to the documentation of this file.
1
3#include "sf_wrappers.h"
4#include "sf_modloader.h"
5#include "sf_hooks.h"
6
7#define JSMN_PARENT_LINKS
8#include "jsmn.h"
9
10void init_building(Building *building)
11{
12 memset(building, 0, sizeof(Building));
13 building->id = -1;
14 building->building_required = -1;
15 building->can_enter = false;
16 building->center_x = -1;
17 building->center_y = -1;
18 building->collision_count = -1;
19 building->description_id = -1;
20 building->flags = -1;
21 building->health = -1;
22 building->name_id = -1;
23 building->race = -1;
24 building->slot_count = -1;
25 building->resource_count = 0;
26}
27
28static bool json_token_streq(const char *json, const jsmntok_t *token, const char *s)
29{
30 return token->type == JSMN_STRING && (int)strlen(s) == token->end - token->start && strncmp(json + token->start, s,
31 token->end -
32 token->start) == 0;
33}
34
35static int jsonint(const char *json, const jsmntok_t *token)
36{
37 char temp[32];
38 int len = token->end - token->start;
39 if (len >= 32)
40 len = 31;
41 strncpy(temp, json + token->start, len);
42 temp[len] = '\0';
43 return atoi(temp);
44}
45
46// Converts a JSON token to a boolean.
47static bool jsonbool(const char *json, const jsmntok_t *token)
48{
49 if (json_token_streq(json, token, "true"))
50 {
51 return true;
52 }
53 return false;
54}
55
56// Skips a token and all of its children, returning the next index to parse.
57static int skip_token_tree(const jsmntok_t *tokens, int token_count, int index)
58{
59 if (index >= token_count)
60 {
61 return index;
62 }
63
64 if (tokens[index].type == JSMN_PRIMITIVE || tokens[index].type == JSMN_STRING)
65 {
66 return index + 1;
67 }
68 else if (tokens[index].type == JSMN_OBJECT || tokens[index].type == JSMN_ARRAY)
69 {
70 int children_count = tokens[index].size;
71 int next_index = index + 1;
72 for (int i = 0; i < children_count; i++)
73 {
74 if (next_index >= token_count)
75 {
76 return token_count;
77 }
78 if (tokens[index].type == JSMN_OBJECT)
79 {
80 // For objects, we have a key-value pair, so we skip two children at a time.
81 next_index = skip_token_tree(tokens, token_count, next_index); // Skip key
82 next_index = skip_token_tree(tokens, token_count, next_index); // Skip value
83 }
84 else
85 {
86 // For arrays, we skip one child at a time.
87 next_index = skip_token_tree(tokens, token_count, next_index);
88 }
89 }
90 return next_index;
91 }
92 return index + 1;
93}
94
95// Maps a JSON key string to the FieldKey enum.
96static FieldKey parse_field_key(const char *json, const jsmntok_t *token)
97{
98 if (token->type != JSMN_STRING)
99 {
100 return FIELD_UNKNOWN;
101 }
102 if (json_token_streq(json, token, "id"))
103 return FIELD_ID;
104 if (json_token_streq(json, token, "building_required"))
106 if (json_token_streq(json, token, "can_enter"))
107 return FIELD_CAN_ENTER;
108 if (json_token_streq(json, token, "center_x"))
109 return FIELD_CENTER_X;
110 if (json_token_streq(json, token, "center_y"))
111 return FIELD_CENTER_Y;
112 if (json_token_streq(json, token, "collision_count"))
114 if (json_token_streq(json, token, "collisions"))
115 return FIELD_COLLISIONS;
116 if (json_token_streq(json, token, "description_id"))
118 if (json_token_streq(json, token, "flags"))
119 return FIELD_FLAGS;
120 if (json_token_streq(json, token, "health"))
121 return FIELD_HEALTH;
122 if (json_token_streq(json, token, "name_id"))
123 return FIELD_NAME_ID;
124 if (json_token_streq(json, token, "race"))
125 return FIELD_RACE;
126 if (json_token_streq(json, token, "resources"))
127 return FIELD_RESOURCES;
128 if (json_token_streq(json, token, "slot_count"))
129 return FIELD_SLOT_COUNT;
130 if (json_token_streq(json, token, "shadow"))
131 return FIELD_SHADOW;
132 if (json_token_streq(json, token, "points"))
133 return FIELD_POINTS;
134 if (json_token_streq(json, token, "amount"))
135 return FIELD_AMOUNT;
136 if (json_token_streq(json, token, "type"))
137 return FIELD_TYPE;
138 if (json_token_streq(json, token, "list"))
139 return FIELD_LIST;
140 if (json_token_streq(json, token, "number"))
141 return FIELD_NUMBER;
142 if (json_token_streq(json, token, "x"))
143 return FIELD_X;
144 if (json_token_streq(json, token, "y"))
145 return FIELD_Y;
146 return FIELD_UNKNOWN;
147}
148
149// Forward declarations for parsing functions
150bool parse_collision(const char *json, const jsmntok_t *tokens, int token_count, int start_index, Collision *collision);
151bool parse_resource(const char *json, const jsmntok_t *tokens, int token_count, int start_index, Resource *resource);
152bool parse_resources(const char *json, const jsmntok_t *tokens, int token_count, int start_index, Building *building);
153bool parse_world_coord(const char *json, const jsmntok_t *tokens, int token_count, int start_index, WorldCoord *coord);
154
155// Parses a single WorldCoord object.
156bool parse_world_coord(const char *json, const jsmntok_t *tokens, int token_count, int start_index, WorldCoord *coord)
157{
158 if (start_index >= token_count || tokens[start_index].type != JSMN_OBJECT)
159 {
160 return false;
161 }
162 int pair_count = tokens[start_index].size;
163 int current_index = start_index + 1;
164 for (int i = 0; i < pair_count; i++)
165 {
166 if (current_index >= token_count)
167 return false;
168 FieldKey key = parse_field_key(json, &tokens[current_index]);
169 if (key == FIELD_UNKNOWN)
170 {
171 current_index = skip_token_tree(tokens, token_count, current_index);
172 current_index = skip_token_tree(tokens, token_count, current_index);
173 continue;
174 }
175
176 const jsmntok_t *value_token = &tokens[current_index + 1];
177 if (value_token->type != JSMN_PRIMITIVE)
178 {
179 current_index = skip_token_tree(tokens, token_count, current_index);
180 current_index = skip_token_tree(tokens, token_count, current_index);
181 continue;
182 }
183
184 switch (key)
185 {
186 case FIELD_X:
187 coord->X = jsonint(json, value_token);
188 break;
189 case FIELD_Y:
190 coord->Y = jsonint(json, value_token);
191 break;
192 default:
193 break;
194 }
195 current_index = skip_token_tree(tokens, token_count, current_index);
196 current_index = skip_token_tree(tokens, token_count, current_index);
197 }
198 return true;
199}
200
201// Parses a single Collision object, including its nested points array.
202bool parse_collision(const char *json, const jsmntok_t *tokens, int token_count, int start_index, Collision *collision)
203{
204 if (start_index >= token_count || tokens[start_index].type != JSMN_OBJECT)
205 {
206 return false;
207 }
208 collision->point_count = 0;
209 int pair_count = tokens[start_index].size;
210 int current_index = start_index + 1;
211 for (int i = 0; i < pair_count; i++)
212 {
213 if (current_index >= token_count)
214 return false;
215 FieldKey key = parse_field_key(json, &tokens[current_index]);
216
217 switch (key)
218 {
219 case FIELD_SHADOW:
220 collision->shadow = jsonbool(json, &tokens[current_index + 1]);
221 break;
222 case FIELD_POINTS:
223 {
224 int points_array_index = current_index + 1;
225 if (points_array_index >= token_count || tokens[points_array_index].type != JSMN_ARRAY)
226 {
227 break;
228 }
229 int point_count = tokens[points_array_index].size;
230 if (point_count > MAX_POINTS)
231 point_count = MAX_POINTS;
232
233 int point_index = points_array_index + 1;
234 for (int j = 0; j < point_count; j++)
235 {
236 if (point_index >= token_count)
237 break;
238 if (parse_world_coord(json, tokens, token_count, point_index, &collision->points[j]))
239 {
240 collision->point_count++;
241 }
242 point_index = skip_token_tree(tokens, token_count, point_index);
243 }
244 break;
245 }
246 default:
247 break;
248 }
249 current_index = skip_token_tree(tokens, token_count, current_index);
250 current_index = skip_token_tree(tokens, token_count, current_index);
251 }
252 return true;
253}
254
255// Parses a single Resource object.
256bool parse_resource(const char *json, const jsmntok_t *tokens, int token_count, int start_index, Resource *resource)
257{
258 if (start_index >= token_count || tokens[start_index].type != JSMN_OBJECT)
259 {
260 return false;
261 }
262 int pair_count = tokens[start_index].size;
263 int current_index = start_index + 1;
264 for (int i = 0; i < pair_count; i++)
265 {
266 if (current_index >= token_count)
267 return false;
268 FieldKey key = parse_field_key(json, &tokens[current_index]);
269
270 switch (key)
271 {
272 case FIELD_AMOUNT:
273 resource->amount = jsonint(json, &tokens[current_index + 1]);
274 break;
275 case FIELD_TYPE:
276 {
277 const jsmntok_t *type_token = &tokens[current_index + 1];
278 int len = type_token->end - type_token->start;
279 if (len >= 32)
280 len = 31;
281 strncpy(resource->type, json + type_token->start, len);
282 resource->type[len] = '\0';
283 }
284 break;
285 default:
286 break;
287 }
288 current_index = skip_token_tree(tokens, token_count, current_index);
289 current_index = skip_token_tree(tokens, token_count, current_index);
290 }
291 return true;
292}
293
294// Parses the resources object and its nested list.
295bool parse_resources(const char *json, const jsmntok_t *tokens, int token_count, int start_index, Building *building)
296{
297 if (start_index >= token_count || tokens[start_index].type != JSMN_OBJECT)
298 {
299 return false;
300 }
301 building->resource_count = 0;
302 int pair_count = tokens[start_index].size;
303 int current_index = start_index + 1;
304 for (int i = 0; i < pair_count; i++)
305 {
306 if (current_index >= token_count)
307 return false;
308 FieldKey key = parse_field_key(json, &tokens[current_index]);
309
310 switch (key)
311 {
312 case FIELD_NUMBER:
313 // No need to parse this, as we'll count the actual items in the list.
314 break;
315 case FIELD_LIST:
316 {
317 int list_array_index = current_index + 1;
318 if (list_array_index >= token_count || tokens[list_array_index].type != JSMN_ARRAY)
319 {
320 break;
321 }
322 int list_count = tokens[list_array_index].size;
323 if (list_count > MAX_RESOURCES)
324 list_count = MAX_RESOURCES;
325
326 int list_item_index = list_array_index + 1;
327 for (int j = 0; j < list_count; j++)
328 {
329 if (list_item_index >= token_count)
330 break;
331 if (parse_resource(json, tokens, token_count, list_item_index, &building->resources[j]))
332 {
333 building->resource_count++;
334 }
335 list_item_index = skip_token_tree(tokens, token_count, list_item_index);
336 }
337 break;
338 }
339 default:
340 break;
341 }
342 current_index = skip_token_tree(tokens, token_count, current_index);
343 current_index = skip_token_tree(tokens, token_count, current_index);
344 }
345 return true;
346}
347
348bool parse_building_from_tokens(const char *json, jsmntok_t *tokens, int token_count, Building *building);
349
350bool parse_building_json(const char *json_string, Building *building)
351{
352 jsmn_parser parser;
353 jsmntok_t *tokens;
354
355 jsmn_init(&parser);
356 int token_count = jsmn_parse(&parser, json_string, strlen(json_string), NULL, 0);
357 tokens = (jsmntok_t *)calloc(token_count, sizeof (jsmntok_t));
358 jsmn_init(&parser);
359 token_count = jsmn_parse(&parser, json_string, strlen(json_string), tokens, token_count);
360
361 // jsmn_parse returns a negative value on error
362 if (token_count < 0)
363 {
364 log_info("Failed to parse due to Negative token count (Enable DEBUG HIGH to view raw JSON string.)");
365 log_debug(DEBUG_HIGH, json_string);
366 return false;
367 }
368
369 // A valid building object must have at least one token (the object itself)
370 if (token_count < 1)
371 {
372 log_info("No object found in JSON file. (Enable DEBUG HIGH to view raw JSON string.)");
373 log_debug(DEBUG_HIGH, json_string);
374 return false;
375 }
376
377 // Delegate the actual struct population to the helper function
378 return parse_building_from_tokens(json_string, tokens, token_count, building);
379}
380
381bool parse_building_from_tokens(const char *json, jsmntok_t *tokens, int token_count, Building *building)
382{
383 // The root of the JSON must be an object.
384 if (tokens[0].type != JSMN_OBJECT)
385 {
386 return false;
387 }
388
389 init_building(building);
390
391 int pairs_to_process = tokens[0].size;
392 // Start iterating from the first key, which is at index 1
393 int current_token_index = 1;
394
395 for (int i = 0; i < pairs_to_process; i++)
396 {
397 // Boundary check for the key
398 if (current_token_index >= token_count)
399 return false;
400
401 FieldKey key = parse_field_key(json, &tokens[current_token_index]);
402 int value_index = current_token_index + 1;
403
404 // Boundary check for the value
405 if (value_index >= token_count)
406 return false;
407
408 switch (key)
409 {
410 case FIELD_ID:
411 building->id = jsonint(json, &tokens[value_index]);
412 building->found_id = true;
413 break;
415 building->building_required = jsonint(json, &tokens[value_index]);
416 building->found_building_required = true;
417 break;
418 case FIELD_CAN_ENTER:
419 building->can_enter = jsonbool(json, &tokens[value_index]);
420 building->found_can_enter = true;
421 break;
422 case FIELD_CENTER_X:
423 building->center_x = jsonint(json, &tokens[value_index]);
424 building->found_center_x = true;
425 break;
426 case FIELD_CENTER_Y:
427 building->center_y = jsonint(json, &tokens[value_index]);
428 building->found_center_y = true;
429 break;
431 building->collision_count = jsonint(json, &tokens[value_index]);
432 building->found_collision_count = true;
433 break;
435 building->description_id = jsonint(json, &tokens[value_index]);
436 building->found_description_id = true;
437 break;
438 case FIELD_FLAGS:
439 building->flags = jsonint(json, &tokens[value_index]);
440 building->found_flags = true;
441 break;
442 case FIELD_HEALTH:
443 building->health = jsonint(json, &tokens[value_index]);
444 building->found_health = true;
445 break;
446 case FIELD_NAME_ID:
447 building->name_id = jsonint(json, &tokens[value_index]);
448 building->found_name_id = true;
449 break;
450 case FIELD_RACE:
451 building->race = jsonint(json, &tokens[value_index]);
452 building->found_race = true;
453 break;
454 case FIELD_SLOT_COUNT:
455 building->slot_count = jsonint(json, &tokens[value_index]);
456 building->found_slot_count = true;
457 break;
458 case FIELD_COLLISIONS:
459 {
460 building->collision_count = 0;
461 int collisions_array_index = value_index;
462 if (tokens[collisions_array_index].type == JSMN_ARRAY)
463 {
464 int collisions_to_parse = tokens[collisions_array_index].size;
465 if (collisions_to_parse > MAX_COLLISIONS)
466 collisions_to_parse = MAX_COLLISIONS;
467
468 int collision_token_index = collisions_array_index + 1;
469 for (int j = 0; j < collisions_to_parse; j++)
470 {
471 if (parse_collision(json, tokens, token_count, collision_token_index, &building->collisions[j]))
472 {
473 building->collision_count++;
474 }
475 collision_token_index = skip_token_tree(tokens, token_count, collision_token_index);
476 }
477 }
478 break;
479 }
480 case FIELD_RESOURCES:
481 parse_resources(json, tokens, token_count, value_index, building);
482 break;
483 default:
484 // Unknown key, just skip it
485 break;
486 }
487
488 // Advance the current index past the key and its entire value tree
489 current_token_index = skip_token_tree(tokens, token_count, current_token_index); // Skip key
490 current_token_index = skip_token_tree(tokens, token_count, current_token_index); // Skip value
491 }
492
493 return true;
494}
495
496char *readfile(const char *path)
497{
498 FILE *file = fopen(path, "rb");
499 if(file == NULL)
500 {
501 log_error("Expected file \"%s\" not found\n", path);
502 return NULL;
503 }
504
505 fseek(file,0,SEEK_END);
506 long len = ftell(file);
507 fseek(file,0,SEEK_SET);
508
509 if(len <= 0)
510 {
511 log_error("Empty or unreadable file: \"%s\"\n",path);
512 fclose(file);
513 return NULL;
514 }
515
516 // To ensure C++ doesn't warn us here
517 char *buffer = (char *) malloc(len+1);
518 if(buffer == NULL)
519 {
520 log_error("Unable to allocate memory for file: \"%s\"\n",path);
521 fclose(file);
522 return NULL;
523 }
524
525 size_t read_len = fread(buffer, 1, len, file);
526 fclose(file);
527
528 if(read_len != (size_t)len)
529 {
530 log_error("File Read Incomplete: \"%s\" (Expected %ld bytes, got %ld bytes)\n", path, len, (long) read_len);
531 free(buffer);
532 return NULL;
533 }
534
535 buffer[read_len] = '\0';
536
537 char *last_brace = strrchr(buffer, '}');
538 if(last_brace != NULL)
539 {
540 last_brace[1] = '\0';
541 }
542 else
543 {
544 log_error("Invalid JSON: No closing brace found in \"%s\"\n", path);
545 free(buffer);
546 return NULL;
547 }
548
549 return buffer;
550}
551
552bool parse_building_json_entrypoint(const char *building_json_name, const char *mod_id, Building *out_building)
553{
554 char currentDir[MAX_PATH];
555 GetCurrentDirectory(MAX_PATH, currentDir);
556 char path[MAX_PATH];
557 snprintf(path, sizeof(path), "%s\\sfsf\\%s\\buildings\\%s.json", currentDir, mod_id, building_json_name);
558
559 char *json_str = readfile(path);
560 if (!json_str)
561 {
562 log_error("Failed to read: %s", path);
563 return false;
564 }
565
566 memset(out_building, 0, sizeof(Building));
567 bool success = parse_building_json(json_str, out_building);
568 if (!success)
569 {
570 log_error("Building JSON structure invalid: %s", building_json_name);
571 }
572
573 free(json_str);
574 return success;
575}
576
577// Print building information
578void print_building(const Building *building)
579{
580 log_debug(DEBUG_MED,"\n=== BUILDING INFORMATION ===\n");
581
582 if (building->found_id)
583 log_debug(DEBUG_MED,"ID: %d\n", building->id);
584 if (building->found_building_required)
585 log_debug(DEBUG_MED,"Building Required: %d\n", building->building_required);
586 if (building->found_can_enter)
587 log_debug(DEBUG_MED,"Can Enter: %s\n", building->can_enter ? "true" : "false");
588 if (building->found_center_x)
589 log_debug(DEBUG_MED,"Center X: %d\n", building->center_x);
590 if (building->found_center_y)
591 log_debug(DEBUG_MED,"Center Y: %d\n", building->center_y);
592 if (building->found_collision_count)
593 log_debug(DEBUG_MED,"Collision Count: %d\n", building->collision_count);
594 if (building->found_description_id)
595 log_debug(DEBUG_MED,"Description ID: %d\n", building->description_id);
596 if (building->found_flags)
597 log_debug(DEBUG_MED,"Flags: %d\n", building->flags);
598 if (building->found_health)
599 log_debug(DEBUG_MED,"Health: %d\n", building->health);
600 if (building->found_name_id)
601 log_debug(DEBUG_MED,"Name ID: %d\n", building->name_id);
602 if (building->found_race)
603 log_debug(DEBUG_MED,"Race: %d\n", building->race);
604 if (building->found_slot_count)
605 log_debug(DEBUG_MED,"Slot Count: %d\n", building->slot_count);
606
607 // Print collisions
608 log_debug(DEBUG_MED,"Collisions:\n");
609 for (int i = 0; i < building->collision_count && i < MAX_COLLISIONS; i++)
610 {
611 log_debug(DEBUG_MED," Collision %d (shadow: %s):\n", i, building->collisions[i].shadow ? "true" : "false");
612 for (int j = 0; j < building->collisions[i].point_count; j++)
613 {
614 log_debug(DEBUG_MED," Point %d: (%d, %d)\n", j,
615 building->collisions[i].points[j].X,
616 building->collisions[i].points[j].Y);
617 }
618 }
619
620 // Print resources
621 if (building->resource_count > 0)
622 {
623 log_debug(DEBUG_MED,"Resources:\n");
624 for (int i = 0; i < building->resource_count; i++)
625 {
626 log_debug(DEBUG_MED," Resource %d: %d %s\n", i,
627 building->resources[i].amount,
628 building->resources[i].type);
629 }
630 }
631
632 log_debug(DEBUG_MED,"============================\n");
633}
void log_error(const char *format,...)
void log_debug(DebugLevel level, const char *format,...)
void log_info(const char *format,...)
@ JSMN_PRIMITIVE
Definition jsmn.h:51
@ JSMN_OBJECT
Definition jsmn.h:48
@ JSMN_ARRAY
Definition jsmn.h:49
@ JSMN_STRING
Definition jsmn.h:50
void jsmn_init(jsmn_parser *parser)
Definition jsmn.h:456
int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const unsigned int num_tokens)
Definition jsmn.h:265
bool parse_collision(const char *json, const jsmntok_t *tokens, int token_count, int start_index, Collision *collision)
bool parse_resources(const char *json, const jsmntok_t *tokens, int token_count, int start_index, Building *building)
char * readfile(const char *path)
void print_building(const Building *building)
bool parse_building_from_tokens(const char *json, jsmntok_t *tokens, int token_count, Building *building)
bool parse_building_json(const char *json_string, Building *building)
bool parse_building_json_entrypoint(const char *building_json_name, const char *mod_id, Building *out_building)
void init_building(Building *building)
bool parse_resource(const char *json, const jsmntok_t *tokens, int token_count, int start_index, Resource *resource)
bool parse_world_coord(const char *json, const jsmntok_t *tokens, int token_count, int start_index, WorldCoord *coord)
#define MAX_RESOURCES
#define MAX_COLLISIONS
#define MAX_POINTS
@ FIELD_SHADOW
@ FIELD_RESOURCES
@ FIELD_CENTER_X
@ FIELD_UNKNOWN
@ FIELD_NUMBER
@ FIELD_LIST
@ FIELD_DESCRIPTION_ID
@ FIELD_RACE
@ FIELD_X
@ FIELD_ID
@ FIELD_CENTER_Y
@ FIELD_HEALTH
@ FIELD_TYPE
@ FIELD_CAN_ENTER
@ FIELD_NAME_ID
@ FIELD_Y
@ FIELD_SLOT_COUNT
@ FIELD_BUILDING_REQUIRED
@ FIELD_POINTS
@ FIELD_FLAGS
@ FIELD_COLLISIONS
@ FIELD_COLLISION_COUNT
@ FIELD_AMOUNT
Resource resources[5]
bool found_collision_count
bool found_description_id
Collision collisions[5]
bool found_building_required
WorldCoord points[255]
int start
Definition jsmn.h:71
int size
Definition jsmn.h:73
int end
Definition jsmn.h:72
jsmntype_t type
Definition jsmn.h:70