Crossfire Server, Branch 1.12  R12190
button.c
Go to the documentation of this file.
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 }