Crossfire Server, Branch 1.12
R12190
|
00001 /* 00002 * static char *rcsid_button_c = 00003 * "$Id: button.c 11578 2009-02-23 22:02:27Z lalo $"; 00004 */ 00005 00006 /* 00007 CrossFire, A Multiplayer game for X-windows 00008 00009 Copyright (C) 2002-2006 Mark Wedel & Crossfire Development Team 00010 Copyright (C) 1992 Frank Tore Johansen 00011 00012 This program is free software; you can redistribute it and/or modify 00013 it under the terms of the GNU General Public License as published by 00014 the Free Software Foundation; either version 2 of the License, or 00015 (at your option) any later version. 00016 00017 This program is distributed in the hope that it will be useful, 00018 but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 GNU General Public License for more details. 00021 00022 You should have received a copy of the GNU General Public License 00023 along with this program; if not, write to the Free Software 00024 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 00025 00026 The authors can be reached via e-mail at crossfire-devel@real-time.com 00027 */ 00028 00034 #include <global.h> 00035 #include <sproto.h> 00036 00037 static objectlink *get_button_links(const object *button); 00038 00052 void trigger_connected(objectlink *ol, object *cause, const int state) { 00053 object *tmp; 00054 00055 for (; ol; ol = ol->next) { 00056 if (!ol->ob || ol->ob->count != ol->id) { 00057 LOG(llevError, "Internal error in trigger_connect. No object associated with link id (%d) (cause='%s'.\n", ol->id, (cause && cause->name) ? cause->name : ""); 00058 continue; 00059 } 00060 /* a button link object can become freed when the map is saving. As 00061 * a map is saved, objects are removed and freed, and if an object is 00062 * on top of a button, this function is eventually called. If a map 00063 * is getting moved out of memory, the status of buttons and levers 00064 * probably isn't important - it will get sorted out when the map is 00065 * re-loaded. As such, just exit this function if that is the case. 00066 */ 00067 00068 if (QUERY_FLAG(ol->ob, FLAG_FREED)) 00069 return; 00070 tmp = ol->ob; 00071 00072 /* if the criteria isn't appropriate, don't do anything */ 00073 if (state && !QUERY_FLAG(tmp, FLAG_ACTIVATE_ON_PUSH)) 00074 continue; 00075 if (!state && !QUERY_FLAG(tmp, FLAG_ACTIVATE_ON_RELEASE)) 00076 continue; 00077 00078 /* 00079 * (tchize) call the triggers of the activated object. 00080 * tmp = activated object 00081 * op is activator (aka button) 00082 */ 00083 if (execute_event(tmp, EVENT_TRIGGER, cause, NULL, NULL, SCRIPT_FIX_ALL) != 0) 00084 continue; 00085 00086 switch (tmp->type) { 00087 case GATE: 00088 case HOLE: 00089 tmp->value = tmp->stats.maxsp ? !state : state; 00090 tmp->speed = 0.5; 00091 update_ob_speed(tmp); 00092 break; 00093 00094 case CF_HANDLE: 00095 SET_ANIMATION(tmp, (tmp->value = tmp->stats.maxsp ? !state : state)); 00096 update_object(tmp, UP_OBJ_FACE); 00097 break; 00098 00099 case SIGN: 00100 if (!tmp->stats.food || tmp->last_eat < tmp->stats.food) { 00101 ext_info_map(NDI_UNIQUE|NDI_NAVY, tmp->map, 00102 MSG_TYPE_SIGN, MSG_SUBTYPE_NONE, 00103 tmp->msg, tmp->msg); 00104 if (tmp->stats.food) 00105 tmp->last_eat++; 00106 } 00107 break; 00108 00109 case ALTAR: 00110 tmp->value = 1; 00111 SET_ANIMATION(tmp, tmp->value); 00112 update_object(tmp, UP_OBJ_FACE); 00113 break; 00114 00115 case BUTTON: 00116 case PEDESTAL: 00117 tmp->value = state; 00118 SET_ANIMATION(tmp, tmp->value); 00119 update_object(tmp, UP_OBJ_FACE); 00120 break; 00121 00122 case TIMED_GATE: 00123 tmp->speed = tmp->arch->clone.speed; 00124 update_ob_speed(tmp); /* original values */ 00125 tmp->value = tmp->arch->clone.value; 00126 tmp->stats.sp = 1; 00127 tmp->stats.hp = tmp->stats.maxhp; 00128 /* Handle multipart gates. We copy the value for the other parts 00129 * from the head - this ensures that the data will consistent 00130 */ 00131 for (tmp = tmp->more; tmp != NULL; tmp = tmp->more) { 00132 tmp->speed = tmp->head->speed; 00133 tmp->value = tmp->head->value; 00134 tmp->stats.sp = tmp->head->stats.sp; 00135 tmp->stats.hp = tmp->head->stats.hp; 00136 update_ob_speed(tmp); 00137 } 00138 break; 00139 00140 case DIRECTOR: 00141 case FIREWALL: 00142 if (!QUERY_FLAG(tmp, FLAG_ANIMATE)&&tmp->type == FIREWALL) 00143 move_firewall(tmp); 00144 else { 00145 if ((tmp->stats.sp += tmp->stats.maxsp) > 8) /* next direction */ 00146 tmp->stats.sp = ((tmp->stats.sp-1)%8)+1; 00147 animate_turning(tmp); 00148 } 00149 break; 00150 00151 default: 00152 ob_trigger(tmp, cause, state); 00153 } 00154 } 00155 } 00156 00165 void push_button(object *op) { 00166 /* LOG(llevDebug, "push_button: %s (%d)\n", op->name, op->count); */ 00167 trigger_connected(get_button_links(op), op, op->value); 00168 } 00169 00178 void update_button(object *op) { 00179 object *ab, *tmp, *head; 00180 int tot, any_down = 0, old_value = op->value; 00181 objectlink *ol; 00182 00183 /* LOG(llevDebug, "update_button: %s (%d)\n", op->name, op->count); */ 00184 for (ol = get_button_links(op); ol; ol = ol->next) { 00185 if (!ol->ob || ol->ob->count != ol->id) { 00186 LOG(llevDebug, "Internal error in update_button (%s).\n", op->name); 00187 continue; 00188 } 00189 00190 tmp = ol->ob; 00191 if (tmp->type == BUTTON) { 00192 for (ab = tmp->above, tot = 0; ab != NULL; ab = ab->above) 00193 /* Bug? The pedestal code below looks for the head of 00194 * the object, this bit doesn't. I'd think we should check 00195 * for head here also. Maybe it also makese sense to 00196 * make the for ab=tmp->above loop common, and alter 00197 * behaviour based on object within that loop? 00198 */ 00199 00200 /* Basically, if the move_type matches that on what the 00201 * button wants, we count it. The second check is so that 00202 * objects don't move (swords, etc) will count. Note that 00203 * this means that more work is needed to make buttons 00204 * that are only triggered by flying objects. 00205 */ 00206 if ((ab->move_type&tmp->move_on) || ab->move_type == 0) 00207 tot += ab->weight*(ab->nrof ? ab->nrof : 1)+ab->carrying; 00208 00209 tmp->value = (tot >= tmp->weight) ? 1 : 0; 00210 if (tmp->value) 00211 any_down = 1; 00212 } else if (tmp->type == PEDESTAL) { 00213 tmp->value = 0; 00214 for (ab = tmp->above; ab != NULL; ab = ab->above) { 00215 head = ab->head ? ab->head : ab; 00216 /* Same note regarding move_type for buttons above apply here. */ 00217 if (((head->move_type&tmp->move_on) || ab->move_type == 0) 00218 && (head->race == tmp->slaying 00219 || ((head->type == SPECIAL_KEY) && (head->slaying == tmp->slaying)) 00220 || (!strcmp(tmp->slaying, "player") && head->type == PLAYER))) 00221 tmp->value = 1; 00222 } 00223 if (tmp->value) 00224 any_down = 1; 00225 } 00226 } 00227 if (any_down) /* If any other buttons were down, force this to remain down */ 00228 op->value = 1; 00229 00230 /* If this button hasn't changed, don't do anything */ 00231 if (op->value != old_value) { 00232 SET_ANIMATION(op, op->value); 00233 update_object(op, UP_OBJ_FACE); 00234 push_button(op); /* Make all other buttons the same */ 00235 } 00236 } 00237 00241 void update_buttons(mapstruct *m) { 00242 objectlink *ol; 00243 oblinkpt *obp; 00244 00245 for (obp = m->buttons; obp; obp = obp->next) 00246 for (ol = obp->link; ol; ol = ol->next) { 00247 if (!ol->ob || ol->ob->count != ol->id) { 00248 LOG(llevError, "Internal error in update_button (%s (%dx%d):%d, connected %ld).\n", 00249 ol->ob ? ol->ob->name : "null", 00250 ol->ob ? ol->ob->x : -1, 00251 ol->ob ? ol->ob->y : -1, 00252 ol->id, 00253 obp->value); 00254 continue; 00255 } 00256 if (ol->ob->type == BUTTON || ol->ob->type == PEDESTAL) { 00257 update_button(ol->ob); 00258 break; 00259 } 00260 } 00261 } 00262 00268 void use_trigger(object *op) { 00269 /* Toggle value */ 00270 op->value = !op->value; 00271 push_button(op); 00272 } 00273 00284 void animate_turning(object *op) { 00285 if (++op->state >= NUM_ANIMATIONS(op)/8) 00286 op->state = 0; 00287 SET_ANIMATION(op, (op->stats.sp-1)*NUM_ANIMATIONS(op)/8+op->state); 00288 update_object(op, UP_OBJ_FACE); 00289 } 00290 00291 #define ARCH_SACRIFICE(xyz) ((xyz)->slaying) 00292 #define NROF_SACRIFICE(xyz) ((uint32)(xyz)->stats.food) 00293 00305 static int matches_sacrifice(const object *altar, const object *sacrifice) { 00306 char name[MAX_BUF]; 00307 00308 if (QUERY_FLAG(sacrifice, FLAG_ALIVE) 00309 || QUERY_FLAG(sacrifice, FLAG_IS_LINKED) 00310 || sacrifice->type == PLAYER) 00311 return 0; 00312 00313 query_base_name(sacrifice, 0, name, MAX_BUF); 00314 if (ARCH_SACRIFICE(altar) == sacrifice->arch->name 00315 || ARCH_SACRIFICE(altar) == sacrifice->name 00316 || ARCH_SACRIFICE(altar) == sacrifice->slaying 00317 || (!strcmp(ARCH_SACRIFICE(altar), name))) 00318 return 1; 00319 00320 if (strcmp(ARCH_SACRIFICE(altar), "money") == 0 00321 && sacrifice->type == MONEY) 00322 return 1; 00323 00324 return 0; 00325 } 00326 00355 int check_altar_sacrifice(const object *altar, const object *sacrifice, int remove_others, int *toremove) { 00356 int money; 00357 object *tmp; 00358 int wanted, rest; 00359 object *above; 00360 00361 if (!matches_sacrifice(altar, sacrifice)) 00362 /* New dropped object doesn't match the altar, other objects already on top are not enough to 00363 * activate altar, else they would have disappeared. */ 00364 return 0; 00365 00366 /* Check item is paid for. */ 00367 if (QUERY_FLAG(sacrifice, FLAG_UNPAID)) { 00368 return 0; 00369 } 00370 00371 money = (strcmp(ARCH_SACRIFICE(altar), "money") == 0) ? 1 : 0; 00372 00373 /* Easy checks: newly dropped object is enough for sacrifice. */ 00374 if (money && sacrifice->nrof*sacrifice->value >= NROF_SACRIFICE(altar)) { 00375 if (toremove) { 00376 *toremove = NROF_SACRIFICE(altar)/sacrifice->value; 00377 /* Round up any sacrifices. Altars don't make change either */ 00378 if (NROF_SACRIFICE(altar)%sacrifice->value) 00379 (*toremove)++; 00380 } 00381 return 1; 00382 } 00383 00384 if (!money && NROF_SACRIFICE(altar) <= (sacrifice->nrof ? sacrifice->nrof : 1)) { 00385 if (toremove) 00386 *toremove = NROF_SACRIFICE(altar); 00387 return 1; 00388 } 00389 00390 if (money) { 00391 wanted = NROF_SACRIFICE(altar)-sacrifice->nrof*sacrifice->value; 00392 } else { 00393 wanted = NROF_SACRIFICE(altar)-(sacrifice->nrof ? sacrifice->nrof : 1); 00394 } 00395 rest = wanted; 00396 00397 /* Ok, now we check if we got enough with other items. 00398 * We only check items above altar, and not checking again sacrifice. 00399 */ 00400 for (tmp = altar->above; tmp != NULL && wanted > 0; tmp = tmp->above) { 00401 if (tmp == sacrifice || !matches_sacrifice(altar, tmp)) 00402 continue; 00403 if (money) 00404 wanted -= tmp->nrof*tmp->value; 00405 else 00406 wanted -= (tmp->nrof ? tmp->nrof : 1); 00407 } 00408 00409 if (wanted > 0) 00410 /* Not enough value, let's bail out. */ 00411 return 0; 00412 00413 /* From there on, we do have enough objects for the altar. */ 00414 00415 /* Last dropped object will be totally eaten in any case. */ 00416 if (toremove) 00417 *toremove = sacrifice->nrof ? sacrifice->nrof : 1; 00418 00419 if (!remove_others) 00420 return 1; 00421 00422 /* We loop again, this time to remove what we need. */ 00423 for (tmp = altar->above; tmp != NULL && rest > 0; tmp = above) { 00424 above = tmp->above; 00425 if (tmp == sacrifice || !matches_sacrifice(altar, tmp)) 00426 continue; 00427 if (money) { 00428 wanted = tmp->nrof*tmp->value; 00429 if (rest > wanted) { 00430 remove_ob(tmp); 00431 rest -= wanted; 00432 } else { 00433 wanted = rest/tmp->value; 00434 if (rest%tmp->value) 00435 wanted++; 00436 decrease_ob_nr(tmp, wanted); 00437 return 1; 00438 } 00439 } else 00440 if (rest > (tmp->nrof ? tmp->nrof : 1)) { 00441 rest -= (tmp->nrof ? tmp->nrof : 1); 00442 remove_ob(tmp); 00443 } else { 00444 decrease_ob_nr(tmp, rest); 00445 return 1; 00446 } 00447 } 00448 00449 /* Something went wrong, we'll be nice and accept the sacrifice anyway. */ 00450 LOG(llevError, "check_altar_sacrifice on %s: found objects to sacrifice, but couldn't remove them??\n", altar->map->path); 00451 return 1; 00452 } 00453 00470 int operate_altar(object *altar, object **sacrifice) { 00471 int number; 00472 00473 if (!altar->map) { 00474 LOG(llevError, "BUG: operate_altar(): altar has no map\n"); 00475 return 0; 00476 } 00477 00478 if (!altar->slaying || altar->value) 00479 return 0; 00480 00481 if (!check_altar_sacrifice(altar, *sacrifice, 1, &number)) 00482 return 0; 00483 00484 /* check_altar_sacrifice fills in number for us. */ 00485 *sacrifice = decrease_ob_nr(*sacrifice, number); 00486 00487 if (altar->msg) 00488 ext_info_map(NDI_BLACK, altar->map, MSG_TYPE_DIALOG, MSG_TYPE_DIALOG_ALTAR, altar->msg, altar->msg); 00489 return 1; 00490 } 00491 00495 static void trigger_move(object *op, int state) { /* 1 down and 0 up */ 00496 op->stats.wc = state; 00497 if (state) { 00498 use_trigger(op); 00499 if (op->stats.exp > 0) /* check sanity */ 00500 op->speed = 1.0/op->stats.exp; 00501 else 00502 op->speed = 1.0; 00503 update_ob_speed(op); 00504 op->speed_left = -1; 00505 } else { 00506 use_trigger(op); 00507 op->speed = 0; 00508 update_ob_speed(op); 00509 } 00510 } 00511 00525 int check_trigger(object *op, object *cause) { 00526 object *tmp; 00527 int push = 0, tot = 0; 00528 int in_movement = op->stats.wc || op->speed; 00529 00530 switch (op->type) { 00531 case TRIGGER_BUTTON: 00532 if (op->weight > 0) { 00533 if (cause) { 00534 for (tmp = op->above; tmp; tmp = tmp->above) 00535 /* Comment reproduced from update_buttons(): 00536 * Basically, if the move_type matches that on what the 00537 * button wants, we count it. The second check is so that 00538 * objects that don't move (swords, etc) will count. Note that 00539 * this means that more work is needed to make buttons 00540 * that are only triggered by flying objects. 00541 */ 00542 if ((tmp->move_type&op->move_on) || tmp->move_type == 0) { 00543 tot += tmp->weight*(tmp->nrof ? tmp->nrof : 1)+tmp->carrying; 00544 } 00545 if (tot >= op->weight) 00546 push = 1; 00547 if (op->stats.ac == push) 00548 return 0; 00549 op->stats.ac = push; 00550 if (NUM_ANIMATIONS(op) > 1) { 00551 SET_ANIMATION(op, push); 00552 update_object(op, UP_OBJ_FACE); 00553 } 00554 if (in_movement || !push) 00555 return 0; 00556 } 00557 trigger_move(op, push); 00558 } 00559 return 0; 00560 00561 case TRIGGER_PEDESTAL: 00562 if (cause) { 00563 for (tmp = op->above; tmp; tmp = tmp->above) { 00564 object *head = tmp->head ? tmp->head : tmp; 00565 00566 /* See comment in TRIGGER_BUTTON about move_types */ 00567 if (((head->move_type&op->move_on) || head->move_type == 0) 00568 && (head->race == op->slaying || (!strcmp(op->slaying, "player") && head->type == PLAYER))) { 00569 push = 1; 00570 break; 00571 } 00572 } 00573 if (op->stats.ac == push) 00574 return 0; 00575 op->stats.ac = push; 00576 if (NUM_ANIMATIONS(op) > 1) { 00577 SET_ANIMATION(op, push); 00578 update_object(op, UP_OBJ_FACE); 00579 } 00580 if (in_movement || !push) 00581 return 0; 00582 } 00583 trigger_move(op, push); 00584 return 0; 00585 00586 case TRIGGER_ALTAR: 00587 if (cause) { 00588 if (in_movement) 00589 return 0; 00590 if (operate_altar(op, &cause)) { 00591 if (NUM_ANIMATIONS(op) > 1) { 00592 SET_ANIMATION(op, 1); 00593 update_object(op, UP_OBJ_FACE); 00594 } 00595 00596 if (op->last_sp >= 0) { 00597 trigger_move(op, 1); 00598 if (op->last_sp > 0) 00599 op->last_sp = -op->last_sp; 00600 } else { 00601 /* for trigger altar with last_sp, the ON/OFF 00602 * status (-> +/- value) is "simulated": 00603 */ 00604 op->value = !op->value; 00605 trigger_move(op, 1); 00606 op->last_sp = -op->last_sp; 00607 op->value = !op->value; 00608 } 00609 return cause == NULL; 00610 } else { 00611 return 0; 00612 } 00613 } else { 00614 if (NUM_ANIMATIONS(op) > 1) { 00615 SET_ANIMATION(op, 0); 00616 update_object(op, UP_OBJ_FACE); 00617 } 00618 00619 /* If trigger_altar has "last_sp > 0" set on the map, 00620 * it will push the connected value only once per sacrifice. 00621 * Otherwise (default), the connected value will be 00622 * pushed twice: First by sacrifice, second by reset! -AV 00623 */ 00624 if (!op->last_sp) 00625 trigger_move(op, 0); 00626 else { 00627 op->stats.wc = 0; 00628 op->value = !op->value; 00629 op->speed = 0; 00630 update_ob_speed(op); 00631 } 00632 } 00633 return 0; 00634 00635 case TRIGGER: 00636 if (cause) { 00637 if (in_movement) 00638 return 0; 00639 push = 1; 00640 } 00641 if (NUM_ANIMATIONS(op) > 1) { 00642 SET_ANIMATION(op, push); 00643 update_object(op, UP_OBJ_FACE); 00644 } 00645 trigger_move(op, push); 00646 return 1; 00647 00648 default: 00649 LOG(llevDebug, "Unknown trigger type: %s (%d)\n", op->name, op->type); 00650 return 0; 00651 } 00652 } 00653 00663 void add_button_link(object *button, mapstruct *map, int connected) { 00664 oblinkpt *obp; 00665 objectlink *ol = get_objectlink(); 00666 00667 if (!map) { 00668 LOG(llevError, "Tried to add button-link without map.\n"); 00669 free_objectlink(ol); 00670 return; 00671 } 00672 00673 SET_FLAG(button, FLAG_IS_LINKED); 00674 00675 ol->ob = button; 00676 ol->id = button->count; 00677 00678 for (obp = map->buttons; obp && obp->value != connected; obp = obp->next) 00679 ; 00680 00681 if (obp) { 00682 ol->next = obp->link; 00683 obp->link = ol; 00684 } else { 00685 obp = get_objectlinkpt(); 00686 obp->value = connected; 00687 00688 obp->next = map->buttons; 00689 map->buttons = obp; 00690 obp->link = ol; 00691 } 00692 } 00693 00700 void remove_button_link(object *op) { 00701 oblinkpt *obp; 00702 objectlink **olp, *ol; 00703 00704 if (op->map == NULL) { 00705 LOG(llevError, "remove_button_link() in object without map.\n"); 00706 return; 00707 } 00708 if (!QUERY_FLAG(op, FLAG_IS_LINKED)) { 00709 LOG(llevError, "remove_button_linked() in unlinked object.\n"); 00710 return; 00711 } 00712 00713 for (obp = op->map->buttons; obp; obp = obp->next) 00714 for (olp = &obp->link; (ol = *olp); olp = &ol->next) 00715 if (ol->ob == op) { 00716 /* LOG(llevDebug, "Removed link %d in button %s and map %s.\n", 00717 obp->value, op->name, op->map->path); 00718 */ 00719 *olp = ol->next; 00720 free(ol); 00721 return; 00722 } 00723 LOG(llevError, "remove_button_linked(): couldn't find object.\n"); 00724 CLEAR_FLAG(op, FLAG_IS_LINKED); 00725 } 00726 00734 static objectlink *get_button_links(const object *button) { 00735 oblinkpt *obp; 00736 objectlink *ol; 00737 00738 if (!button->map) 00739 return NULL; 00740 00741 for (obp = button->map->buttons; obp; obp = obp->next) 00742 for (ol = obp->link; ol; ol = ol->next) 00743 if (ol->ob == button && ol->id == button->count) 00744 return obp->link; 00745 return NULL; 00746 } 00747 00756 int get_button_value(const object *button) { 00757 oblinkpt *obp; 00758 objectlink *ol; 00759 00760 if (!button->map) 00761 return 0; 00762 00763 for (obp = button->map->buttons; obp; obp = obp->next) 00764 for (ol = obp->link; ol; ol = ol->next) 00765 if (ol->ob == button && ol->id == button->count) 00766 return obp->value; 00767 return 0; 00768 } 00769 00788 object *check_inv_recursive(object *op, const object *trig) { 00789 object *tmp, *ret = NULL; 00790 00791 /* First check the object itself. */ 00792 if ((!trig->stats.hp || (op->type == trig->stats.hp)) 00793 && (!trig->slaying || (op->slaying == trig->slaying)) 00794 && (!trig->race || (op->arch->name == trig->race)) 00795 && (!trig->title || (op->title == trig->title))) 00796 return op; 00797 00798 for (tmp = op->inv; tmp; tmp = tmp->below) { 00799 if (tmp->inv) { 00800 ret = check_inv_recursive(tmp, trig); 00801 if (ret) 00802 return ret; 00803 } else if ((!trig->stats.hp || (tmp->type == trig->stats.hp)) 00804 && (!trig->slaying || (tmp->slaying == trig->slaying)) 00805 && (!trig->race || (tmp->arch->name == trig->race)) 00806 && (!trig->title || (tmp->title == trig->title))) 00807 return tmp; 00808 } 00809 00810 return NULL; 00811 } 00812 00828 void check_inv(object *op, object *trig) { 00829 object *match; 00830 00831 if (op->type != PLAYER) 00832 return; 00833 00834 match = check_inv_recursive(op, trig); 00835 if (match && trig->last_sp) { 00836 if (trig->last_heal) 00837 decrease_ob(match); 00838 use_trigger(trig); 00839 } else if (!match && !trig->last_sp) 00840 use_trigger(trig); 00841 } 00842 00853 void verify_button_links(const mapstruct *map) { 00854 oblinkpt *obp; 00855 objectlink *ol; 00856 00857 if (!map) 00858 return; 00859 00860 for (obp = map->buttons; obp; obp = obp->next) { 00861 for (ol = obp->link; ol; ol = ol->next) { 00862 if (ol->id != ol->ob->count) 00863 LOG(llevError, "verify_button_links: object %s on list is corrupt (%d!=%d)\n", ol->ob->name, ol->id, ol->ob->count); 00864 } 00865 } 00866 }