Atrinik Server 2.5
skills/construction.c
Go to the documentation of this file.
00001 /************************************************************************
00002 *            Atrinik, a Multiplayer Online Role Playing Game            *
00003 *                                                                       *
00004 *    Copyright (C) 2009-2011 Alex Tokar and Atrinik Development Team    *
00005 *                                                                       *
00006 * Fork from Daimonin (Massive Multiplayer Online Role Playing Game)     *
00007 * and Crossfire (Multiplayer game for X-windows).                       *
00008 *                                                                       *
00009 * This program is free software; you can redistribute it and/or modify  *
00010 * it under the terms of the GNU General Public License as published by  *
00011 * the Free Software Foundation; either version 2 of the License, or     *
00012 * (at your option) any later version.                                   *
00013 *                                                                       *
00014 * This program is distributed in the hope that it will be useful,       *
00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00017 * GNU General Public License for more details.                          *
00018 *                                                                       *
00019 * You should have received a copy of the GNU General Public License     *
00020 * along with this program; if not, write to the Free Software           *
00021 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.             *
00022 *                                                                       *
00023 * The author can be reached at admin@atrinik.org                        *
00024 ************************************************************************/
00025 
00030 #include <global.h>
00031 
00039 static int can_build_over(mapstruct *m, object *new_item, int x, int y)
00040 {
00041     object *tmp;
00042 
00043     for (tmp = GET_MAP_OB(m, x, y); tmp; tmp = tmp->above)
00044     {
00045         tmp = HEAD(tmp);
00046 
00047         if (QUERY_FLAG(tmp, FLAG_IS_BUILDABLE) || QUERY_FLAG(tmp, FLAG_SYS_OBJECT))
00048         {
00049             continue;
00050         }
00051 
00052         switch (new_item->type)
00053         {
00054             case SIGN:
00055                 /* Allow signs to be built on books. */
00056                 if (tmp->type != BOOK)
00057                 {
00058                     return 0;
00059                 }
00060 
00061                 break;
00062 
00063             default:
00064                 return 0;
00065         }
00066     }
00067 
00068     /* If item being built is multi-tile, need to check other parts too. */
00069     if (new_item->more)
00070     {
00071         return can_build_over(m, new_item->more, x + new_item->more->arch->clone.x - new_item->arch->clone.x, y + new_item->more->arch->clone.y - new_item->arch->clone.y);
00072     }
00073 
00074     return 1;
00075 }
00076 
00083 static object *get_wall(mapstruct *m, int x, int y)
00084 {
00085     object *tmp;
00086 
00087     for (tmp = GET_MAP_OB_LAYER(m, x, y, LAYER_WALL - 1); tmp && tmp->layer == LAYER_WALL; tmp = tmp->above)
00088     {
00089         if (tmp->type == WALL)
00090         {
00091             return tmp;
00092         }
00093     }
00094 
00095     return NULL;
00096 }
00097 
00105 static int builder_floor(object *op, object *new_floor, int x, int y)
00106 {
00107     object *tmp;
00108 
00109     for (tmp = GET_MAP_OB_LAYER(op->map, x, y, LAYER_FLOOR - 1); tmp && tmp->layer == LAYER_FLOOR; tmp = tmp->above)
00110     {
00111         if (tmp->type == FLOOR || QUERY_FLAG(tmp, FLAG_IS_FLOOR))
00112         {
00113             if (tmp->arch == new_floor->arch)
00114             {
00115                 new_draw_info(0, COLOR_WHITE, op, "You feel too lazy to redo the exact same floor.");
00116                 return 0;
00117             }
00118 
00119             remove_ob(tmp);
00120             break;
00121         }
00122     }
00123 
00124     if (tmp && QUERY_FLAG(tmp, FLAG_UNIQUE))
00125     {
00126         SET_FLAG(new_floor, FLAG_UNIQUE);
00127     }
00128 
00129     SET_FLAG(new_floor, FLAG_IS_FLOOR);
00130     new_floor->type = FLOOR;
00131     new_floor->x = x;
00132     new_floor->y = y;
00133     insert_ob_in_map(new_floor, op->map, NULL, 0);
00134     new_draw_info(0, COLOR_WHITE, op, "You change the floor to better suit your tastes.");
00135 
00136     return 1;
00137 }
00138 
00146 static int builder_item(object *op, object *new_item, int x, int y)
00147 {
00148     object *floor;
00149     int w = wall_blocked(op->map, x, y);
00150 
00151     /* If it's not a wallmask, don't allow building on top of blocked squares. */
00152     if (new_item->type != WALL && w)
00153     {
00154         new_draw_info_format(0, COLOR_WHITE, op, "Something is blocking you from building the %s on that square.", query_name(new_item, NULL));
00155         return 0;
00156     }
00157     /* Wallmask, only allow building on top of blocked square that only
00158      * contains a wall. */
00159     else if (new_item->type == WALL)
00160     {
00161         object *wall_ob = get_wall(op->map, x, y);
00162 
00163         if (!w || !wall_ob)
00164         {
00165             new_draw_info_format(0, COLOR_WHITE, op, "The %s can only be built on top of a wall.", query_name(new_item, NULL));
00166             return 0;
00167         }
00168         else if (wall_ob->above && wall_ob->above->type == WALL)
00169         {
00170             new_draw_info_format(0, COLOR_WHITE, op, "You first need to remove the %s before building on top of that wall again.", query_name(wall_ob->above, NULL));
00171             return 0;
00172         }
00173     }
00174 
00175     /* Only allow building if there is a floor. */
00176     for (floor = GET_MAP_OB_LAYER(op->map, x, y, LAYER_FLOOR - 1); floor && floor->layer == LAYER_FLOOR; floor = floor->above)
00177     {
00178         if (floor->type == FLOOR || QUERY_FLAG(floor, FLAG_IS_FLOOR))
00179         {
00180             break;
00181         }
00182     }
00183 
00184     if (!floor)
00185     {
00186         new_draw_info(0, COLOR_WHITE, op, "This square has no floor, you can't build here.");
00187         return 0;
00188     }
00189 
00190     /* For signs, take the msg from a book on the square. */
00191     if (new_item->type == SIGN)
00192     {
00193         object *book;
00194 
00195         for (book = GET_MAP_OB(op->map, x, y); book; book = book->above)
00196         {
00197             if (book->type == BOOK)
00198             {
00199                 break;
00200             }
00201         }
00202 
00203         if (!book || (!book->msg && !book->custom_name))
00204         {
00205             new_draw_info(0, COLOR_WHITE, op, "You need to put a book with your message (or custom name) on the floor.");
00206             return 0;
00207         }
00208 
00209         /* If the book has a custom name, copy it to the sign's name. */
00210         if (book->custom_name)
00211         {
00212             FREE_AND_COPY_HASH(new_item->name, book->custom_name);
00213         }
00214 
00215         /* Copy the message. */
00216         if (book->msg)
00217         {
00218             FREE_AND_COPY_HASH(new_item->msg, book->msg);
00219         }
00220 
00221         remove_ob(book);
00222     }
00223 
00224     /* If the item is turnable, adjust direction. */
00225     if (QUERY_FLAG(new_item, FLAG_IS_TURNABLE) && op->facing)
00226     {
00227         new_item->direction = op->facing;
00228         SET_ANIMATION(new_item, (NUM_ANIMATIONS(new_item) / NUM_FACINGS(new_item)) * new_item->direction);
00229     }
00230 
00231     SET_FLAG(new_item, FLAG_NO_PICK);
00232     new_item->x = x;
00233     new_item->y = y;
00234     insert_ob_in_map(new_item, op->map, NULL, 0);
00235     new_draw_info_format(0, COLOR_WHITE, op, "You build the %s.", query_name(new_item, NULL));
00236 
00237     return 1;
00238 }
00239 
00248 static int wall_split_orientation(const object *wall_ob, char *wall_name, size_t wall_name_size, char *orientation, size_t orientation_size)
00249 {
00250     int l;
00251 
00252     strncpy(wall_name, wall_ob->arch->name, wall_name_size - 1);
00253 
00254     /* Extract the wall name, which is the text up to the last '_'. */
00255     for (l = wall_name_size; l >= 0; l--)
00256     {
00257         if (wall_name[l] == '_')
00258         {
00259             /* Copy over orientation. */
00260             strncpy(orientation, wall_name + l, orientation_size - 1);
00261             wall_name[l] = '\0';
00262             return 1;
00263         }
00264     }
00265 
00266     return 0;
00267 }
00268 
00277 static void fix_walls(mapstruct *map, int x, int y)
00278 {
00279     int connect_val;
00280     object *wall_ob;
00281     char wall_name[MAX_BUF], orientation[MAX_BUF];
00282     uint32 old_flags[NUM_FLAGS_32];
00283     archetype *new_arch;
00284     int flag;
00285 
00286     /* First, find the wall on that spot */
00287     wall_ob = get_wall(map, x, y);
00288 
00289     if (!wall_ob || !wall_split_orientation(wall_ob, wall_name, sizeof(wall_name), orientation, sizeof(orientation)))
00290     {
00291         return;
00292     }
00293 
00294     connect_val = 0;
00295 
00296     if (x > 0 && get_wall(map, x - 1, y))
00297     {
00298         connect_val |= 1;
00299     }
00300 
00301     if (x < MAP_WIDTH(map) - 1 && get_wall(map, x + 1, y))
00302     {
00303         connect_val |= 2;
00304     }
00305 
00306     if (y > 0 && get_wall(map, x, y - 1))
00307     {
00308         connect_val |= 4;
00309     }
00310 
00311     if (y < MAP_HEIGHT(map) - 1 && get_wall(map, x, y + 1))
00312     {
00313         connect_val |= 8;
00314     }
00315 
00316     switch (connect_val)
00317     {
00318         case 0:
00319             return;
00320 
00321         case 10:
00322         case 8:
00323         case 2:
00324             strncat(wall_name, "_8", sizeof(wall_name) - strlen(wall_name) - 1);
00325             break;
00326 
00327         case 11:
00328         case 9:
00329         case 3:
00330         case 1:
00331             strncat(wall_name, "_1", sizeof(wall_name) - strlen(wall_name) - 1);
00332             break;
00333 
00334         case 12:
00335         case 4:
00336         case 14:
00337         case 6:
00338             strncat(wall_name, "_3", sizeof(wall_name) - strlen(wall_name) - 1);
00339             break;
00340 
00341         case 5:
00342         case 7:
00343         case 13:
00344         case 15:
00345             strncat(wall_name, "_4", sizeof(wall_name) - strlen(wall_name) - 1);
00346             break;
00347     }
00348 
00349     /* No need to change anything if the old and new names are identical. */
00350     if (!strncmp(wall_name, wall_ob->arch->name, sizeof(wall_name)))
00351     {
00352         return;
00353     }
00354 
00355     /* Before anything, make sure the archetype does in fact exist... */
00356     new_arch = find_archetype(wall_name);
00357 
00358     if (!new_arch)
00359     {
00360         return;
00361     }
00362 
00363     /* Now delete current wall, and insert new one
00364      * We save flags to avoid any trouble with buildable/non buildable, etc. */
00365     for (flag = 0; flag < NUM_FLAGS_32; flag++)
00366     {
00367         old_flags[flag] = wall_ob->flags[flag];
00368     }
00369 
00370     remove_ob(wall_ob);
00371 
00372     wall_ob = arch_to_object(new_arch);
00373     wall_ob->type = WALL;
00374     wall_ob->x = x;
00375     wall_ob->y = y;
00376     insert_ob_in_map(wall_ob, map, NULL, 0);
00377 
00378     for (flag = 0; flag < NUM_FLAGS_32; flag++)
00379     {
00380         wall_ob->flags[flag] = old_flags[flag];
00381     }
00382 }
00383 
00391 static int builder_wall(object *op, object *new_wall, int x, int y)
00392 {
00393     object *wall_ob = get_wall(op->map, x, y);
00394 
00395     if (wall_ob)
00396     {
00397         char wall_name[MAX_BUF], orientation[MAX_BUF];
00398 
00399         if (!wall_split_orientation(wall_ob, wall_name, sizeof(wall_name), orientation, sizeof(orientation)))
00400         {
00401             new_draw_info(0, COLOR_WHITE, op, "You don't see a way to redecorate that wall.");
00402             return 0;
00403         }
00404 
00405         if (!strncmp(wall_ob->arch->name, wall_name, strlen(wall_name)))
00406         {
00407             new_draw_info(0, COLOR_WHITE, op, "You feel too lazy to redo the exact same wall.");
00408             return 0;
00409         }
00410     }
00411 
00412     new_wall->type = WALL;
00413 
00414     /* If existing wall, replace it, no need to fix other walls */
00415     if (wall_ob)
00416     {
00417         remove_ob(wall_ob);
00418         new_wall->x = x;
00419         new_wall->y = y;
00420         insert_ob_in_map(new_wall, op->map, NULL, 0);
00421         fix_walls(op->map, x, y);
00422         new_draw_info(0, COLOR_WHITE, op, "You redecorate the wall to better suit your tastes.");
00423     }
00424     /* Else insert new wall and fix all walls around */
00425     else
00426     {
00427         int xt, yt;
00428 
00429         new_wall->x = x;
00430         new_wall->y = y;
00431         insert_ob_in_map(new_wall, op->map, NULL, 0);
00432 
00433         for (xt = x - 1; xt <= x + 1; xt++)
00434         {
00435             for (yt = y - 1; yt <= y + 1; yt++)
00436             {
00437                 if (OUT_OF_REAL_MAP(op->map, xt, yt))
00438                 {
00439                     continue;
00440                 }
00441 
00442                 fix_walls(op->map, xt, yt);
00443             }
00444         }
00445 
00446         new_draw_info(0, COLOR_WHITE, op, "You build a wall.");
00447     }
00448 
00449     return 1;
00450 }
00451 
00458 static int builder_window(object *op, int x, int y)
00459 {
00460     object *wall_ob;
00461     char wall_name[MAX_BUF], orientation[MAX_BUF];
00462     archetype *new_arch;
00463     object *window;
00464     uint32 old_flags[NUM_FLAGS_32];
00465     int flag;
00466 
00467     wall_ob = get_wall(op->map, x, y);
00468 
00469     if (!wall_ob)
00470     {
00471         new_draw_info(0, COLOR_WHITE, op, "There is no wall there.");
00472         return 0;
00473     }
00474 
00475     if (!wall_split_orientation(wall_ob, wall_name, sizeof(wall_name), orientation, sizeof(orientation)))
00476     {
00477         new_draw_info(0, COLOR_WHITE, op, "You don't see a way to build a window in that wall.");
00478         return 0;
00479     }
00480     else if (!strcmp(orientation, "_1") && !strcmp(orientation, "_3"))
00481     {
00482         new_draw_info(0, COLOR_WHITE, op, "You cannot build a window in that wall.");
00483         return 0;
00484     }
00485 
00486     strncat(wall_name, "_w", sizeof(wall_name) - strlen(wall_name) - 1);
00487     strncat(wall_name, orientation, sizeof(wall_name) - strlen(wall_name) - 1);
00488 
00489     new_arch = find_archetype(wall_name);
00490 
00491     /* That type of wall doesn't have corresponding window archetype. */
00492     if (!new_arch)
00493     {
00494         new_draw_info(0, COLOR_WHITE, op, "You cannot build a window in that wall.");
00495         return 0;
00496     }
00497 
00498     /* Now delete current wall, and insert new one with a window.
00499      * We save flags to avoid any trouble with buildable/non buildable, etc. */
00500     for (flag = 0; flag < NUM_FLAGS_32; flag++)
00501     {
00502         old_flags[flag] = wall_ob->flags[flag];
00503     }
00504 
00505     remove_ob(wall_ob);
00506 
00507     window = arch_to_object(new_arch);
00508     window->type = WALL;
00509     window->x = x;
00510     window->y = y;
00511     insert_ob_in_map(window, op->map, NULL, 0);
00512 
00513     for (flag = 0; flag < NUM_FLAGS_32; flag++)
00514     {
00515         window->flags[flag] = old_flags[flag];
00516     }
00517 
00518     CLEAR_FLAG(window, FLAG_BLOCKSVIEW);
00519     new_draw_info(0, COLOR_WHITE, op, "You build a window in the wall.");
00520     return 1;
00521 }
00522 
00528 static void construction_builder(object *op, int x, int y)
00529 {
00530     object *material, *new_item;
00531     archetype *new_arch;
00532     int built = 0;
00533 
00534     material = find_marked_object(op);
00535 
00536     if (!material)
00537     {
00538         new_draw_info(0, COLOR_WHITE, op, "You need to mark raw materials to use.");
00539         return;
00540     }
00541 
00542     if (material->type != MATERIAL)
00543     {
00544         new_draw_info(0, COLOR_WHITE, op, "You can't use the marked item to build.");
00545         return;
00546     }
00547 
00548     /* Create a new object from the raw materials. */
00549     new_arch = find_archetype(material->slaying);
00550 
00551     if (!new_arch)
00552     {
00553         LOG(llevBug, "construction_builder(): Unable to find archetype %s.\n", material->slaying);
00554         new_draw_info(0, COLOR_WHITE, op, "You can't use this strange material.");
00555         return;
00556     }
00557 
00558     new_item = arch_to_object(new_arch);
00559     SET_FLAG(new_item, FLAG_IS_BUILDABLE);
00560 
00561     if (!can_build_over(op->map, new_item, x, y))
00562     {
00563         new_draw_info(0, COLOR_WHITE, op, "You can't build there.");
00564         return;
00565     }
00566 
00567     /* Insert the new object in the map. */
00568     switch (material->sub_type)
00569     {
00570         case ST_MAT_FLOOR:
00571             built = builder_floor(op, new_item, x, y);
00572             break;
00573 
00574         case ST_MAT_WALL:
00575             built = builder_wall(op, new_item, x, y);
00576             break;
00577 
00578         case ST_MAT_ITEM:
00579             built = builder_item(op, new_item, x, y);
00580             break;
00581 
00582         case ST_MAT_WIN:
00583             built = builder_window(op, x, y);
00584             break;
00585 
00586         default:
00587             LOG(llevBug, "construction_builder(): Invalid material subtype %d.\n", material->sub_type);
00588             new_draw_info(0, COLOR_WHITE, op, "Don't know how to apply this material, sorry.");
00589             break;
00590     }
00591 
00592     if (built)
00593     {
00594         decrease_ob(material);
00595         fix_player(op);
00596     }
00597 }
00598 
00606 static void construction_destroyer(object *op, int x, int y)
00607 {
00608     object *item;
00609 
00610     for (item = GET_MAP_OB_LAST(op->map, x, y); item; item = item->below)
00611     {
00612         if (QUERY_FLAG(item, FLAG_SYS_OBJECT))
00613         {
00614             continue;
00615         }
00616 
00617         if (item->type != FLOOR && QUERY_FLAG(item, FLAG_IS_BUILDABLE))
00618         {
00619             break;
00620         }
00621     }
00622 
00623     if (!item)
00624     {
00625         new_draw_info(0, COLOR_WHITE, op, "Nothing to remove.");
00626         return;
00627     }
00628 
00629     /* Do not allow destroying containers with inventory. */
00630     if (item->type == CONTAINER && item->inv)
00631     {
00632         new_draw_info_format(0, COLOR_WHITE, op, "You cannot remove the %s, since it contains items.", query_name(item, NULL));
00633         return;
00634     }
00635 
00636     remove_ob(item);
00637 
00638     /* Fix walls around the one that was removed. */
00639     if (item->type == WALL)
00640     {
00641         int xt, yt;
00642 
00643         for (xt = x - 1; xt <= x + 1; xt++)
00644         {
00645             for (yt = y - 1; yt <= y + 1; yt++)
00646             {
00647                 if (!OUT_OF_REAL_MAP(op->map, xt, yt))
00648                 {
00649                     fix_walls(op->map, xt, yt);
00650                 }
00651             }
00652         }
00653     }
00654 
00655     new_draw_info_format(0, COLOR_WHITE, op, "You remove the %s.", query_name(item, NULL));
00656 }
00657 
00662 void construction_do(object *op, int dir)
00663 {
00664     object *skill_item, *floor, *tmp;
00665     int x, y;
00666 
00667     if (op->type != PLAYER)
00668     {
00669         LOG(llevBug, "construction_do(): Construction can only be used by players.\n");
00670         return;
00671     }
00672 
00673     skill_item = CONTR(op)->equipment[PLAYER_EQUIP_SKILL_ITEM];
00674 
00675     if (!skill_item)
00676     {
00677         new_draw_info(0, COLOR_WHITE, op, "You need to apply a skill item to use this skill.");
00678         return;
00679     }
00680 
00681     if (skill_item->stats.sp != SK_CONSTRUCTION)
00682     {
00683         new_draw_info_format(0, COLOR_WHITE, op, "The %s cannot be used with the construction skill.", query_name(skill_item, NULL));
00684         return;
00685     }
00686 
00687     if (!dir)
00688     {
00689         new_draw_info(0, COLOR_WHITE, op, "You can't build or destroy under yourself.");
00690         return;
00691     }
00692 
00693     x = op->x + freearr_x[dir];
00694     y = op->y + freearr_y[dir];
00695 
00696     if ((1 > x) || (1 > y) || ((MAP_WIDTH(op->map) - 2) < x) || ((MAP_HEIGHT(op->map) - 2) < y))
00697     {
00698         new_draw_info(0, COLOR_WHITE, op, "Can't build on map edge.");
00699         return;
00700     }
00701 
00702     /* Check specified square
00703      * The square must have only buildable items. */
00704     floor = GET_MAP_OB(op->map, x, y);
00705 
00706     if (!floor)
00707     {
00708         LOG(llevBug, "construction_do(): Undefined square on map %s (%d, %d)\n", op->map->path, x, y);
00709         new_draw_info(0, COLOR_WHITE, op, "You'd better not build here, it looks weird.");
00710         return;
00711     }
00712 
00713     if (skill_item->sub_type != ST_BD_BUILD)
00714     {
00715         for (tmp = floor; tmp; tmp = tmp->above)
00716         {
00717             if (QUERY_FLAG(tmp, FLAG_SYS_OBJECT))
00718             {
00719                 continue;
00720             }
00721 
00722             if (!QUERY_FLAG(tmp, FLAG_IS_BUILDABLE))
00723             {
00724                 new_draw_info(0, COLOR_WHITE, op, "You can't build there.");
00725                 return;
00726             }
00727         }
00728     }
00729 
00730     /* Prevent players without destroyer from getting themselves stuck. */
00731     if (skill_item->sub_type != ST_BD_REMOVE)
00732     {
00733         int found_destroyer = 0;
00734 
00735         for (tmp = op->inv; tmp; tmp = tmp->below)
00736         {
00737             if (tmp->type == SKILL_ITEM && tmp->sub_type == ST_BD_REMOVE)
00738             {
00739                 found_destroyer = 1;
00740                 break;
00741             }
00742         }
00743 
00744         if (!found_destroyer)
00745         {
00746             new_draw_info(0, COLOR_WHITE, op, "You cannot build without having a destroyer at hand.");
00747             return;
00748         }
00749     }
00750 
00751     switch (skill_item->sub_type)
00752     {
00753         case ST_BD_REMOVE:
00754             construction_destroyer(op, x, y);
00755             break;
00756 
00757         case ST_BD_BUILD:
00758             construction_builder(op, x, y);
00759             break;
00760 
00761         default:
00762             LOG(llevBug, "Skill item %s has invalid subtype.\n", query_name(skill_item, NULL));
00763             new_draw_info(0, COLOR_WHITE, op, "Don't know how to apply this tool, sorry.");
00764             break;
00765     }
00766 }