Crossfire Server, Branch 1.12  R12190
check_1727944.c
Go to the documentation of this file.
00001 /*
00002  * CrossFire, A Multiplayer game for X-windows
00003  *
00004  * Copyright (C) 2007 Crossfire Development Team
00005  * Copyright (C) 1992 Frank Tore Johansen
00006  *
00007  * This program is free software; you can redistribute it and/or modify
00008  * it under the terms of the GNU General Public License as published by
00009  * the Free Software Foundation; either version 2 of the License, or
00010  * (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00020  *
00021  * The authors can be reached via e-mail at crossfire-devel@real-time.com
00022  */
00023 
00034 #include <stdlib.h>
00035 #include <check.h>
00036 #include <global.h>
00037 
00038 void setup(void) {
00039     /* put any initialisation steps here, they will be run before each testcase */
00040 }
00041 
00042 void teardown(void) {
00043     /* put any cleanup steps here, they will be run after each testcase */
00044 }
00045 
00046 #if 0
00047 static mapstruct *get_random_map(mapstruct *map) {
00048     object *exit_ob;
00049     mapstruct *random;
00050     RMParms rp;
00051     char newmap_name[HUGE_BUF], *cp;
00052     static int reference_number = 0;
00053     int x, y;
00054 
00055     exit_ob = NULL;
00056     for (x = 0; x < MAP_WIDTH(map) && exit_ob == NULL; x++) {
00057         for (y = 0; y < MAP_HEIGHT(map) && exit_ob == NULL; y++) {
00058             for (exit_ob = GET_MAP_OB(map, x, y); exit_ob != NULL; exit_ob = exit_ob->above)
00059                 if (exit_ob->type == EXIT && exit_ob->msg != NULL)
00060                     break;
00061         }
00062     }
00063 
00064     if (!exit_ob)
00065         /* this means we reached the end of the random part. */
00066         return NULL;
00067 
00068     /* copied from server/server.c:enter_random_map(). */
00069     memset(&rp, 0, sizeof(RMParms));
00070     rp.Xsize = -1;
00071     rp.Ysize = -1;
00072     rp.region = get_region_by_map(exit_ob->map);
00073     if (exit_ob->msg)
00074         set_random_map_variable(&rp, exit_ob->msg);
00075     rp.origin_x = exit_ob->x;
00076     rp.origin_y = exit_ob->y;
00077     strcpy(rp.origin_map, map->path);
00078 
00079     /* If we have a final_map, use it as a base name to give some clue
00080      * as where the player is.  Otherwise, use the origin map.
00081      * Take the last component (after the last slash) to give
00082      * shorter names without bogus slashes.
00083      */
00084     if (rp.final_map[0]) {
00085         cp = strrchr(rp.final_map, '/');
00086         if (!cp)
00087             cp = rp.final_map;
00088     } else {
00089         char buf[HUGE_BUF];
00090 
00091         cp = strrchr(rp.origin_map, '/');
00092         if (!cp)
00093             cp = rp.origin_map;
00094         /* Need to strip of any trailing digits, if it has them */
00095         snprintf(buf, sizeof(buf), "%s", cp);
00096         while (isdigit(buf[strlen(buf)-1]))
00097             buf[strlen(buf)-1] = 0;
00098         cp = buf;
00099     }
00100     snprintf(newmap_name, sizeof(newmap_name), "/random/%s%04d", cp+1, reference_number++);
00101     /* now to generate the actual map. */
00102     return generate_random_map(newmap_name, &rp, NULL);
00103 }
00104 
00105 static void do_run() {
00106     mapstruct *worldmap;
00107     mapstruct *random;
00108     mapstruct *old;
00109     int iteration, x, y, map;
00110     object *check;
00111     char path[150];
00112 
00113     for (map = 1; map <= 3; map++) {
00114         snprintf(path, sizeof(path), "/whalingoutpost/underwaterdungeon/level%d", map);
00115         worldmap = ready_map_name(path, 0);
00116         fail_unless(worldmap != NULL, "Can't load %s", path);
00117 
00118         random = worldmap;
00119         old = NULL;
00120         iteration = 0;
00121         while (random != NULL) {
00122             random = get_random_map(random);
00123             if (!random)
00124                 break;
00125             if (old)
00126                 delete_map(old);
00127             old = random;
00128             iteration++;
00129             for (x = 0; x < MAP_WIDTH(random); x++) {
00130                 for (y = 0; y < MAP_HEIGHT(random); y++) {
00131                     for (check = GET_MAP_OB(random, x, y); check; check = check->above) {
00132                         if (check->type == HORN && check->title && strcmp(check->title, "of Plenty") == 0)
00133                             fail_unless(check->inv != NULL, "Horn has empty inventory!");
00134                     }
00135                 }
00136             }
00137         }
00138         fail_unless(iteration != 0, "did %d iterations", iteration);
00139         if (old)
00140             delete_map(old);
00141     }
00142 }
00143 #endif
00144 
00145 #if 0
00146 static void do_run() {
00147     mapstruct *map, *overlay;
00148     int x, y, found = 0, test = 0;
00149     object *check;
00150 
00151     overlay = ready_map_name("../../rsc/bug_1727944_unique", MAP_PLAYER_UNIQUE);
00152     fail_unless(overlay != NULL, "Couldn't load unique map ../../rsc/bug_1727944_unique");
00153 
00154     while (found == 0 && test < 10) {
00155         map = ready_map_name("../../rsc/bug_1727944", MAP_PLAYER_UNIQUE);
00156         fail_unless(map != NULL, "couldn't load map ../../rsc/bug_1727944");
00157 
00158         for (x = 0; x < MAP_WIDTH(map); x++) {
00159             for (y = 0; y < MAP_HEIGHT(map); y++) {
00160                 for (check = GET_MAP_OB(map, x, y); check; check = check->above) {
00161                     if (check->type == HORN) {
00162                         fail_unless(check->inv != NULL, "Horn has empty inventory!");
00163                         fail_unless(check->inv->below == NULL, "Horn has 2 items in inventory!");
00164                         if (check->title && strcmp(check->title, "of Plenty") == 0) {
00165                             remove_ob(check);
00166                             insert_ob_in_map_at(check, overlay, NULL, 0, 2, 3);
00167                             found++;
00168                             break;
00169                         }
00170                     }
00171                 }
00172             }
00173         }
00174         delete_map(map);
00175         test++;
00176     }
00177     save_map(overlay, SAVE_MODE_OVERLAY);
00178     delete_map(overlay);
00179 }
00180 #endif
00181 
00182 extern int artifact_init;
00183 extern int arch_init;
00184 
00185 /* Copied from loader.l */
00186 extern const char *const spell_mapping[];
00187 
00188 static void local_check_loaded_object(object *op) {
00189     int ip;
00190 
00191     if (artifact_init)
00192         /* Artifacts are special beasts, let's not check them. */
00193         return;
00194 
00195     /* We do some specialized handling to handle legacy cases of name_pl.
00196      * If the object doesn't have a name_pl, we just use the object name -
00197      * this isn't perfect (things won't be properly pluralized), but works to
00198      * that degree (5 heart is still quite understandable).  But the case we
00199      * also have to catch is if this object is not using the normal name for
00200      * the object.  In that case, we also want to use the loaded name.
00201      * Otherwise, what happens is that the the plural name will lose
00202      * information (appear as just 'hearts' and not 'goblins heart')
00203      */
00204     if (op->arch && op->name != op->arch->clone.name && op->name_pl == op->arch->clone.name_pl) {
00205         if (op->name_pl)
00206             free_string(op->name_pl);
00207         op->name_pl = NULL;
00208     }
00209     if (!op->name_pl && op->name)
00210         op->name_pl = add_string(op->name);
00211 
00212     /* objects now have a materialname.  try to patch it in */
00213     if (!(IS_WEAPON(op) && op->level > 0)) {
00214         if (op->map != NULL)
00215             set_materialname(op, op->map->difficulty, NULL);
00216         else
00217             set_materialname(op, 5, NULL);
00218     }
00219     /* only do these when program is first run - a bit
00220      * excessive to do this at every run - most of this is
00221      * really just to catch any errors - program will still run, but
00222      * not in the ideal fashion.
00223      */
00224     if ((op->type == WEAPON || op->type == BOW) && arch_init) {
00225         if (!op->skill) {
00226             LOG(llevError, "Weapon %s lacks a skill.\n", op->name);
00227         } else if ((!strcmp(op->skill, "one handed weapons") && op->body_info[1] != -1)
00228         || (!strcmp(op->skill, "two handed weapons") && op->body_info[1] != -2)) {
00229             LOG(llevError, "weapon %s arm usage does not match skill: %d, %s\n",
00230             op->name, op->body_info[1], op->skill);
00231         }
00232     }
00233 
00234     /* We changed last_heal to gen_sp_armour, which is what it
00235      * really does for many objects.  Need to catch any in maps
00236      * that may have an old value.
00237      */
00238     if ((op->type == WEAPON)
00239     || (op->type == ARMOUR)
00240     || (op->type == HELMET)
00241     || (op->type == SHIELD)
00242     || (op->type == RING)
00243     || (op->type == BOOTS)
00244     || (op->type == GLOVES)
00245     || (op->type == AMULET)
00246     || (op->type == GIRDLE)
00247     || (op->type == BRACERS)
00248     || (op->type == CLOAK)) {
00249         if (op->last_heal) {
00250             LOG(llevDebug, "Object %s still has last_heal set, not gen_sp_armour\n", op->name ? op->name : "NULL");
00251             op->gen_sp_armour = op->last_heal;
00252             op->last_heal = 0;
00253         }
00254         ip = calc_item_power(op, 0);
00255         /* Legacy objects from before item power was in the game */
00256         if (!op->item_power && ip) {
00257             if (ip > 3) {
00258                 LOG(llevDebug, "Object %s had no item power, using %d\n", op->name ? op->name : "NULL", ip);
00259             }
00260             op->item_power = ip;
00261         }
00262         /* Check for possibly bogus values.  Has to meet both these criteria -
00263         * something that has item_power 1 is probably just fine if our calculated
00264         * value is 1 or 2 - these values are small enough that hard to be precise.
00265         * similarly, it item_power is 0, the first check will always pass,
00266         * but not the second one.
00267         */
00268         if (ip > 2*op->item_power && ip > (op->item_power+3)) {
00269             LOG(llevDebug, "Object %s seems to have too low item power? %d > %d\n", op->name ? op->name : "NULL", ip, op->item_power);
00270         }
00271     }
00272     /* Old spellcasting object - need to load in the appropiate object */
00273     if ((op->type == ROD || op->type == WAND || op->type == SCROLL || op->type == HORN || op->type == FIREWALL || /* POTIONS and ALTARS don't always cast spells, but if they do, update them */ ((op->type == POTION || op->type == ALTAR) && op->stats.sp))
00274     && !op->inv
00275     && !arch_init)  {
00276         object *tmp;
00277 
00278     /* Fireall is bizarre in that spell type was stored in dam.  Rest are 'normal'
00279      * in that spell was stored in sp.
00280      */
00281         tmp = create_archetype(spell_mapping[op->type == FIREWALL ? op->stats.dam : op->stats.sp]);
00282         insert_ob_in_ob(tmp, op);
00283         op->randomitems = NULL; /* So another spell isn't created for this object */
00284     }
00285 
00286     /* spellbooks & runes use slaying.  But not to arch name, but to spell name */
00287     if ((op->type == SPELLBOOK || op->type == RUNE) && op->slaying && !op->inv && !arch_init) {
00288         object *tmp;
00289 
00290         tmp = create_archetype_by_object_name(op->slaying);
00291         insert_ob_in_ob(tmp, op);
00292         op->randomitems = NULL; /* So another spell isn't created for this object */
00293         /* without this, value is all screwed up */
00294         op->value = op->arch->clone.value*op->inv->value;
00295     }
00296 
00297     if (QUERY_FLAG(op, FLAG_MONSTER)) {
00298         if (op->stats.hp > op->stats.maxhp)
00299             LOG(llevDebug, "Monster %s has hp set higher than maxhp (%d>%d)\n", op->name, op->stats.hp, op->stats.maxhp);
00300         }
00301     if ((QUERY_FLAG(op, FLAG_GENERATOR) && QUERY_FLAG(op, FLAG_CONTENT_ON_GEN))
00302         || op->type == CREATOR
00303         || op->type == CONVERTER) {
00304         /* Object will duplicate it's content as part of the
00305          * generation process. To do this, we must flag inventory
00306          * so it remains unevaluated concerning the randomitems and
00307          * the living (a demonlord shouldn't cast from inside generator!)
00308          */
00309         flag_inv(op, FLAG_IS_A_TEMPLATE);
00310     }
00311 
00312     /* Here we'll handle custom monsters. In order to handle them correctly, especially in the fix_object
00313      * method, we'll create a new temporary archetype containing defined values.
00314      * Of course this doesn't apply when loading archetypes or artifacts.
00315      */
00316     if (arch_init == 0 && artifact_init == 0 && QUERY_FLAG(op, FLAG_MONSTER) && op->arch && !can_merge(op, &op->arch->clone)) {
00317         archetype *temp = get_archetype_struct();
00318 
00319         temp->reference_count++;
00320         temp->name = add_string(op->arch->name);
00321         temp->tail_x = op->arch->tail_x;
00322         temp->tail_y = op->arch->tail_y;
00323         copy_object(op, &temp->clone);
00324         temp->clone.inv = NULL;
00325         temp->clone.env = NULL;
00326         temp->clone.x = 0;
00327         temp->clone.y = 0;
00328         temp->clone.map = NULL;
00329         if (FABS(temp->clone.speed) > MIN_ACTIVE_SPEED) {
00330             /* Clone has a speed, so need to clear that because it isn't on a map.
00331              * But we need to keep the value, because otherwise the customized object
00332              * will have no speed (fix_player() will use the 0 value).  So set it
00333              * to zero, call update_ob_speed() to remove it from active list, then
00334              * set its speed back to the original.
00335              */
00336             temp->clone.speed = 0;
00337             update_ob_speed(&temp->clone);
00338             temp->clone.speed = op->speed;
00339         }
00340 
00341         temp->more = op->arch->more;
00342         op->arch = temp;
00343         /* LOG(llevDebug, "created temporary archetype for %s at %d,%d\n", op->name, op->x, op->y); */
00344     }
00345 }
00346 
00347 START_TEST(test_randommaps) {
00348 #if 0
00349     int test;
00350     mapstruct *overlay;
00351     object *check;
00352 
00353     for (test = 0; test < 50; test++)
00354         do_run();
00355 
00356     for (test = 0; test < 50; test++) {
00357         overlay = ready_map_name("../../rsc/bug_1727944_unique", MAP_PLAYER_UNIQUE);
00358         fail_unless(overlay != NULL, "Couldn't load unique map ../../rsc/bug_1727944_unique");
00359         fail_unless(GET_MAP_OB(overlay, 2, 3) != NULL, "No item on spot 2,3?");
00360 
00361         for (check = GET_MAP_OB(overlay, 2, 3)->above; check != NULL; check = check->above) {
00362             fail_unless(check->type == HORN, "Found a non horn?");
00363             fail_unless(check->inv != NULL, "Horn without a spell!");
00364             fail_unless(check->inv->below == NULL, "Horn with 2 items in inventory.");
00365         }
00366         save_map(overlay, SAVE_MODE_OVERLAY);
00367         delete_map(overlay);
00368     }
00369 #endif
00370 
00371 #if 0
00372     int test;
00373     archetype *horn = find_archetype("horn");
00374     fail_unless(horn != NULL, "couldn't find archetype horn.");
00375     archetype *horn2 = find_archetype("horn2");
00376     fail_unless(horn2 != NULL, "couldn't find archetype horn2.");
00377 
00378     for (test = 0; test < 100000; test++) {
00379         object *check = arch_to_object(RANDOM()%2 ? horn : horn2);
00380 
00381         generate_artifact(check, RANDOM()%100);
00382         fail_unless(check->inv != NULL, "horn without inventory!");
00383     }
00384 #endif
00385 
00386     int test, level, found = 0;
00387     object *the_chest, *check;
00388     mapstruct *map;
00389     treasurelist *tlist = find_treasurelist("uncommon_items");
00390     fail_unless(tlist != NULL, "couldn't find treasure list uncommon_items");
00391 
00392     for (test = 0; test < 10; test++) {
00393         for (level = 1; level < 120; level++) {
00394             map = get_empty_map(1, 1);
00395             fail_unless(map != NULL, "failed to get empty map");
00396             map->difficulty = level;
00397 
00398             the_chest = create_archetype("chest");  /* was "chest_2" */
00399             fail_unless(the_chest != NULL, "failed to get chest");
00400             the_chest->randomitems = tlist;
00401             the_chest->stats.hp = RANDOM()%100;
00402             insert_ob_in_map_at(the_chest, map, NULL, 0, 0, 0);
00403             fix_auto_apply(map);
00404             the_chest = GET_MAP_OB(map, 0, 0);
00405             fail_unless(the_chest != NULL, "failed to recover chest?");
00406             for (check = the_chest->inv; check; check = check->below) {
00407                 if (check->type != HORN)
00408                     continue;
00409                 local_check_loaded_object(check);
00410                 fail_unless(check->inv != NULL, "horn without inventory");
00411                 fail_unless(check->inv->below == NULL, "horn with 2 items");
00412                 fail_unless(check->randomitems == NULL, "horn with randomitems set");
00413                 found++;
00414             }
00415             delete_map(map);
00416         }
00417     }
00418     fail_unless(found > 100, "didn't find 100 horn but %d??", found);
00419 
00420 }
00421 END_TEST
00422 
00423 Suite *bug_suite(void) {
00424     Suite *s = suite_create("bug");
00425     TCase *tc_core = tcase_create("Core");
00426 
00427     /*setup and teardown will be called before each test in testcase 'tc_core' */
00428     tcase_add_checked_fixture(tc_core, setup, teardown);
00429 
00430     suite_add_tcase(s, tc_core);
00431     tcase_add_test(tc_core, test_randommaps);
00432     tcase_set_timeout(tc_core, 0);
00433 
00434     return s;
00435 }
00436 
00437 int main(void) {
00438     int nf;
00439     Suite *s = bug_suite();
00440     SRunner *sr = srunner_create(s);
00441 
00442     srunner_set_fork_status(sr, CK_NOFORK);
00443     init(0, NULL);
00444 
00445     srunner_set_xml(sr, LOGDIR "/bugs/bugtrack/1727944.xml");
00446     srunner_set_log(sr, LOGDIR "/bugs/bugtrack/1727944.out");
00447     srunner_run_all(sr, CK_ENV); /*verbosity from env variable*/
00448     nf = srunner_ntests_failed(sr);
00449     srunner_free(sr);
00450     return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
00451 }