/************************************************************************** * # # # ## # # ### ## ## ### http://www.lyonesse.it * * # # # # # ## # # # # # * * # # # # # ## ## # # ## ## ## # # ## * * # # # # # ## # # # # # # # # # # # * * ### # ## # # ### ## ## ### # # #### ## Ver. 1.0 * * * * -Based on CircleMud & Smaug- Copyright (c) 2001-2002 by Mithrandir * * * * ********************************************************************** */ /* ************************************************************************ * File: shop.c Part of CircleMUD * * Usage: shopkeepers: loading config files, spec procs. * * * * All rights reserved. See license.doc for complete information. * * * * Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University * * CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * ************************************************************************ */ /*** * The entire shop rewrite for Circle 3.0 was done by Jeff Fink. Thanks Jeff! ***/ #include "conf.h" #include "sysdep.h" #include "structs.h" #include "comm.h" #include "handler.h" #include "db.h" #include "interpreter.h" #include "utils.h" #include "shop.h" #include "constants.h" /* External variables */ extern TIME_INFO_DATA time_info; /* Forward/External function declarations */ ACMD(do_tell); ACMD(do_action); ACMD(do_echo); ACMD(do_say); void sort_keeper_objs(CHAR_DATA *keeper, int shop_nr); /* Local variables */ SHOP_DATA *shop_index; int top_shop = -1; int cmd_say, cmd_tell, cmd_emote, cmd_slap, cmd_puke; /* local functions */ char *read_shop_message(int mnum, room_vnum shr, FILE *shop_f, const char *why); int read_type_list(FILE *shop_f, SHOP_BUY_DATA *list, int new_format, int max); int read_list(FILE *shop_f, SHOP_BUY_DATA *list, int new_format, int max, int type); void shopping_list(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr); void shopping_value(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr); void shopping_sell(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr); OBJ_DATA *get_selling_obj(CHAR_DATA *ch, char *name, CHAR_DATA *keeper, int shop_nr, int msg); OBJ_DATA *slide_obj(OBJ_DATA *obj, CHAR_DATA *keeper, int shop_nr); void shopping_buy(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr); OBJ_DATA *get_purchase_obj(CHAR_DATA *ch, char *arg, CHAR_DATA *keeper, int shop_nr, int msg); OBJ_DATA *get_hash_obj_vis(CHAR_DATA *ch, char *name, OBJ_DATA *list); OBJ_DATA *get_slide_obj_vis(CHAR_DATA *ch, char *name, OBJ_DATA *list); void boot_the_shops(FILE *shop_f, char *filename, int rec_count); void assign_the_shopkeepers(void); char *customer_string(int shop_nr, int detailed); void list_all_shops(CHAR_DATA *ch); void handle_detailed_list(char *buf, char *buf1, CHAR_DATA *ch); void list_detailed_shop(CHAR_DATA *ch, int shop_nr); void show_shops(CHAR_DATA *ch, char *arg); int is_ok_char(CHAR_DATA *keeper, CHAR_DATA *ch, int shop_nr); int is_open(CHAR_DATA *keeper, int shop_nr, int msg); int is_ok(CHAR_DATA *keeper, CHAR_DATA *ch, int shop_nr); void push(STACK_DATA *stack, int pushval); int top(STACK_DATA *stack); int pop(STACK_DATA *stack); void evaluate_operation(STACK_DATA *ops, STACK_DATA *vals); int find_oper_num(char token); int evaluate_expression(OBJ_DATA *obj, char *expr); int trade_with(OBJ_DATA *item, int shop_nr); int same_obj(OBJ_DATA *obj1, OBJ_DATA *obj2); int shop_producing(OBJ_DATA *item, int shop_nr); int transaction_amt(char *arg); char *times_message(OBJ_DATA *obj, char *name, int num); int buy_price(OBJ_DATA *obj, int shop_nr); int sell_price(CHAR_DATA *ch, OBJ_DATA *obj, int shop_nr); char *list_object(OBJ_DATA *obj, int cnt, int index, int shop_nr); int ok_shop_room(int shop_nr, ROOM_DATA *room); SPECIAL(shop_keeper); int ok_damage_shopkeeper(CHAR_DATA *ch, CHAR_DATA *victim); int add_to_list(SHOP_BUY_DATA *list, int type, int *len, int *val); int end_read_list(SHOP_BUY_DATA *list, int len, int error); void read_line(FILE *shop_f, const char *string, void *data); /* config arrays */ const char *operator_str[] = { "[({", "])}", "|+", "&*", "^'" }; /* Constant list for printing out who we sell to */ const char *trade_letters[] = { "Good", /* First, the alignment based ones */ "Evil", "Neutral", "Magic User", /* Then the class based ones */ "Cleric", "Thief", "Warrior", "Sorcerer", "Human", /* And finally the race based ones */ "Elf", "Dwarf", "\n" }; const char *shop_bits[] = { "WILL_FIGHT", "USES_BANK", "\n" }; /* ======================================================================= */ /* * Function to take a shop and mob and save any non-unique items to it * It saves any object vnums which aren't native producing, good for crashs */ void save_shop_nonnative(shop_vnum shop_num, CHAR_DATA *keeper) { FILE *cfile = NULL; OBJ_DATA *obj; sprintf(buf, "shop_item/%ld", shop_num); if ((cfile = fopen(buf, "w")) == NULL) { mudlog("SYSERR: SHP: Can't write new shop_item file.", BRF, LVL_IMPL, TRUE); exit(0); } /* * Loops through the keeper's item list, checks if its native or not * if it isnt, it writes the item's VNUM to the file */ *buf = '\0'; *buf2 = '\0'; sprintf(buf2, "#%ld\n%s\n", shop_num, GET_NAME(keeper)); for (obj = keeper->first_carrying; obj; obj = obj->next_content) { if (!shop_producing(obj, shop_num) && !OBJ_FLAGGED(obj, ITEM_UNIQUE)) sprintf(buf2 + strlen(buf2), "%ld\n", GET_OBJ_VNUM(obj)); } fprintf(cfile, buf2); fprintf(cfile, "$\n"); fclose(cfile); } void load_shop_nonnative(shop_vnum shop_num, CHAR_DATA *keeper) { FILE *cfile = NULL; OBJ_DATA *obj; int line_num = 0, placer = 0; obj_vnum v_this; sprintf(buf, "shop_item/%ld", shop_num); /* Check to see if we have a file for this shop number */ if ((cfile = fopen(buf, "r")) == NULL) return; // Shop number line_num += get_line(cfile, buf); if (sscanf(buf, "#%d", &placer) != 1) { fprintf(stderr, "Format error in shop_item %ld, line %d.\n", shop_num, line_num); exit(0); } // Name of shopkeeper line_num += get_line(cfile, buf); // Item list while (buf && *buf != '$') { line_num += get_line(cfile, buf); if (!buf || !*buf || *buf == '$') break; if (sscanf(buf, "%d", &placer) != 1) { fprintf(stderr, "Format error in shop_item %ld, line %d.\n", shop_num, line_num); exit(0); } v_this = placer; obj = read_object(v_this, VIRTUAL); slide_obj(obj, keeper, shop_num); } } /* ======================================================================= */ int is_ok_char(CHAR_DATA *keeper, CHAR_DATA *ch, int shop_nr) { char buf[MAX_INPUT_LENGTH]; if (!CAN_SEE(keeper, ch)) { char actbuf[MAX_INPUT_LENGTH] = MSG_NO_SEE_CHAR; do_say(keeper, actbuf, cmd_say, 0); return (FALSE); } if (IS_GOD(ch)) return (TRUE); if ((IS_GOOD(ch) && NOTRADE_GOOD(shop_nr)) || (IS_EVIL(ch) && NOTRADE_EVIL(shop_nr)) || (IS_NEUTRAL(ch) && NOTRADE_NEUTRAL(shop_nr))) { sprintf(buf, "%s %s", GET_NAME(ch), MSG_NO_SELL_ALIGN); do_tell(keeper, buf, cmd_tell, 0); return (FALSE); } if (IS_NPC(ch)) return (TRUE); if ((IS_MAGIC_USER(ch) && NOTRADE_MAGIC_USER(shop_nr)) || (IS_CLERIC(ch) && NOTRADE_CLERIC(shop_nr)) || (IS_THIEF(ch) && NOTRADE_THIEF(shop_nr)) || (IS_WARRIOR(ch) && NOTRADE_WARRIOR(shop_nr)) || (IS_SORCERER(ch) && NOTRADE_SORCERER(shop_nr))) { sprintf(buf, "%s %s", GET_NAME(ch), MSG_NO_SELL_CLASS); do_tell(keeper, buf, cmd_tell, 0); return (FALSE); } if ((IS_HUMAN(ch) && NOTRADE_HUMAN(shop_nr)) || (IS_ELF(ch) && NOTRADE_ELF(shop_nr)) || (IS_DWARF(ch) && NOTRADE_DWARF(shop_nr))) { sprintf(buf, "%s %s", GET_NAME(ch), MSG_NO_SELL_RACE); do_tell(keeper, buf, cmd_tell, 0); return (FALSE); } return (TRUE); } int is_open(CHAR_DATA *keeper, int shop_nr, int msg) { char buf[MAX_INPUT_LENGTH]; *buf = '\0'; if (SHOP_OPEN1(shop_nr) > time_info.hours) strcpy(buf, MSG_NOT_OPEN_YET); else if (SHOP_CLOSE1(shop_nr) < time_info.hours) { if (SHOP_OPEN2(shop_nr) > time_info.hours) strcpy(buf, MSG_NOT_REOPEN_YET); else if (SHOP_CLOSE2(shop_nr) < time_info.hours) strcpy(buf, MSG_CLOSED_FOR_DAY); } if (!*buf) return (TRUE); if (msg) do_say(keeper, buf, cmd_tell, 0); return (FALSE); } int is_ok(CHAR_DATA *keeper, CHAR_DATA *ch, int shop_nr) { if (is_open(keeper, shop_nr, TRUE)) return (is_ok_char(keeper, ch, shop_nr)); else return (FALSE); } void push(STACK_DATA *stack, int pushval) { S_DATA(stack, S_LEN(stack)++) = pushval; } int top(STACK_DATA *stack) { if (S_LEN(stack) > 0) return (S_DATA(stack, S_LEN(stack) - 1)); else return (NOTHING); } int pop(STACK_DATA *stack) { if (S_LEN(stack) > 0) return (S_DATA(stack, --S_LEN(stack))); else { log("SYSERR: Illegal expression %d in shop keyword list.", S_LEN(stack)); return (0); } } void evaluate_operation(STACK_DATA *ops, STACK_DATA *vals) { int oper; if ((oper = pop(ops)) == OPER_NOT) push(vals, !pop(vals)); else { int val1 = pop(vals), val2 = pop(vals); /* Compiler would previously short-circuit these. */ if (oper == OPER_AND) push(vals, val1 && val2); else if (oper == OPER_OR) push(vals, val1 || val2); } } int find_oper_num(char token) { int index; for (index = 0; index <= MAX_OPER; index++) if (strchr(operator_str[index], token)) return (index); return (NOTHING); } int evaluate_expression(OBJ_DATA *obj, char *expr) { STACK_DATA ops, vals; char *ptr, *end, name[MAX_INPUT_LENGTH]; int temp, index; if (!expr || !*expr) /* Allows opening ( first. */ return (TRUE); ops.len = vals.len = 0; ptr = expr; while (*ptr) { if (isspace(*ptr)) ptr++; else { if ((temp = find_oper_num(*ptr)) == NOTHING) { end = ptr; while (*ptr && !isspace(*ptr) && find_oper_num(*ptr) == NOTHING) ptr++; strncpy(name, end, ptr - end); name[ptr - end] = '\0'; for (index = 0; *extra_bits[index] != '\n'; index++) { if (!str_cmp(name, extra_bits[index])) { push(&vals, OBJ_FLAGGED(obj, 1 << index)); break; } } if (*extra_bits[index] == '\n') push(&vals, isname(name, obj->name)); } else { if (temp != OPER_OPEN_PAREN) while (top(&ops) > temp) evaluate_operation(&ops, &vals); if (temp == OPER_CLOSE_PAREN) { if ((temp = pop(&ops)) != OPER_OPEN_PAREN) { log("SYSERR: Illegal parenthesis in shop keyword expression."); return (FALSE); } } else push(&ops, temp); ptr++; } } } while (top(&ops) != NOTHING) evaluate_operation(&ops, &vals); temp = pop(&vals); if (top(&vals) != NOTHING) { log("SYSERR: Extra operands left on shop keyword expression stack."); return (FALSE); } return (temp); } int trade_with(OBJ_DATA *item, int shop_nr) { int counter; if (GET_OBJ_COST(item) < 1) return (OBJECT_NOVAL); if (OBJ_FLAGGED(item, ITEM_NOSELL) || OBJ_FLAGGED(item, ITEM_DONATED) || GET_OBJ_TYPE(item) == ITEM_MONEY) return (OBJECT_NOTOK); for (counter = 0; SHOP_BUYTYPE(shop_nr, counter) != NOTHING; counter++) if (SHOP_BUYTYPE(shop_nr, counter) == GET_OBJ_TYPE(item)) { if (GET_OBJ_VAL(item, 2) == 0 && (GET_OBJ_TYPE(item) == ITEM_WAND || GET_OBJ_TYPE(item) == ITEM_STAFF)) return (OBJECT_DEAD); else if (evaluate_expression(item, SHOP_BUYWORD(shop_nr, counter))) return (OBJECT_OK); } return (OBJECT_NOTOK); } int same_obj(OBJ_DATA *obj1, OBJ_DATA *obj2) { int index; if (!obj1 || !obj2) return (obj1 == obj2); if (GET_OBJ_RNUM(obj1) != GET_OBJ_RNUM(obj2)) return (FALSE); if (GET_OBJ_COST(obj1) != GET_OBJ_COST(obj2)) return (FALSE); if (GET_OBJ_EXTRA(obj1) != GET_OBJ_EXTRA(obj2)) return (FALSE); for (index = 0; index < MAX_OBJ_AFF; index++) if ((obj1->affected[index].location != obj2->affected[index].location) || (obj1->affected[index].modifier != obj2->affected[index].modifier)) return (FALSE); return (TRUE); } int shop_producing(OBJ_DATA *item, int shop_nr) { int counter; if (GET_OBJ_RNUM(item) < 0) return (FALSE); for (counter = 0; SHOP_PRODUCT(shop_nr, counter) != NOTHING; counter++) if (same_obj(item, &obj_proto[SHOP_PRODUCT(shop_nr, counter)])) return (TRUE); return (FALSE); } int transaction_amt(char *arg) { int num; char *buywhat; /* * If we have two arguments, it means 'buy 5 3', or buy 5 of #3. * We don't do that if we only have one argument, like 'buy 5', buy #5. * Code from Andrey Fidrya <andrey@ALEX-UA.COM> */ buywhat = one_argument(arg, buf); if (*buywhat && *buf && is_number(buf)) { num = atoi(buf); strcpy(arg, arg + strlen(buf) + 1); return (num); } return (1); } char *times_message(OBJ_DATA *obj, char *name, int num) { static char buf[256]; char *ptr; int len; if (obj) { strcpy(buf, obj->short_description); len = strlen(obj->short_description); } else { if ((ptr = strchr(name, '.')) == NULL) ptr = name; else ptr++; len = sprintf(buf, "%s %s", AN(ptr), ptr); } if (num > 1) sprintf(buf + len, " (x %d)", num); return (buf); } OBJ_DATA *get_slide_obj_vis(CHAR_DATA *ch, char *name, OBJ_DATA *list) { OBJ_DATA *i, *last_match = NULL; int j, number; char tmpname[MAX_INPUT_LENGTH]; char *tmp; strcpy(tmpname, name); tmp = tmpname; if (!(number = get_number(&tmp))) return (NULL); for (i = list, j = 1; i && (j <= number); i = i->next_content) if (isname(tmp, i->name)) if (CAN_SEE_OBJ(ch, i) && !same_obj(last_match, i)) { if (j == number) return (i); last_match = i; j++; } return (NULL); } OBJ_DATA *get_hash_obj_vis(CHAR_DATA *ch, char *name, OBJ_DATA *list) { OBJ_DATA *loop, *last_obj = NULL; int index; if (is_number(name)) index = atoi(name); else if (is_number(name + 1)) index = atoi(name + 1); else return (NULL); for (loop = list; loop; loop = loop->next_content) if (CAN_SEE_OBJ(ch, loop) && (loop->obj_flags.cost > 0)) if (!same_obj(last_obj, loop)) { if (--index == 0) return (loop); last_obj = loop; } return (NULL); } OBJ_DATA *get_purchase_obj(CHAR_DATA *ch, char *arg, CHAR_DATA *keeper, int shop_nr, int msg) { OBJ_DATA *obj; char name[MAX_INPUT_LENGTH]; one_argument(arg, name); do { if (*name == '#' || is_number(name)) obj = get_hash_obj_vis(ch, name, keeper->first_carrying); else obj = get_slide_obj_vis(ch, name, keeper->first_carrying); if (!obj || GET_OBJ_TYPE(obj) == ITEM_MONEY) { if (msg) { char buf[MAX_INPUT_LENGTH]; sprintf(buf, shop_index[shop_nr].no_such_item1, GET_NAME(ch)); do_tell(keeper, buf, cmd_tell, 0); } return (NULL); } if (GET_OBJ_COST(obj) <= 0) { extract_obj(obj); obj = NULL; } } while (!obj); return (NULL); } int buy_price(OBJ_DATA *obj, int shop_nr) { return (GET_OBJ_COST(obj) * SHOP_BUYPROFIT(shop_nr)); } void shopping_buy(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr) { OBJ_DATA *obj, *last_obj = NULL; char tempstr[MAX_INPUT_LENGTH], buf[MAX_STRING_LENGTH]; int goldamt = 0, buynum, bought = 0; if (!is_ok(keeper, ch, shop_nr)) return; if (SHOP_SORT(shop_nr) < IS_CARRYING_N(keeper)) sort_keeper_objs(keeper, shop_nr); if ((buynum = transaction_amt(arg)) < 0) { sprintf(buf, "%s A negative amount? Try selling me something.", GET_NAME(ch)); do_tell(keeper, buf, cmd_tell, 0); return; } if (!*arg || !buynum) { sprintf(buf, "%s What do you want to buy??", GET_NAME(ch)); do_tell(keeper, buf, cmd_tell, 0); return; } if (!(obj = get_purchase_obj(ch, arg, keeper, shop_nr, TRUE))) return; if (buy_price(obj, shop_nr) > get_gold(ch) && !IS_GOD(ch)) { char actbuf[MAX_INPUT_LENGTH]; sprintf(buf, shop_index[shop_nr].missing_cash2, GET_NAME(ch)); do_tell(keeper, buf, cmd_tell, 0); switch (SHOP_BROKE_TEMPER(shop_nr)) { case 0: do_action(keeper, strcpy(actbuf, GET_NAME(ch)), cmd_puke, 0); return; case 1: do_echo(keeper, strcpy(actbuf, "smokes on his joint."), cmd_emote, SCMD_EMOTE); return; default: return; } } if (IS_CARRYING_N(ch) + 1 > CAN_CARRY_N(ch)) { sprintf(buf, "%s: You can't carry any more items.\r\n", fname(obj->name)); send_to_char(buf, ch); return; } if (IS_CARRYING_W(ch) + get_real_obj_weight(obj) > CAN_CARRY_W(ch)) { sprintf(buf, "%s: You can't carry that much weight.\r\n", fname(obj->name)); send_to_char(buf, ch); return; } while (obj && (GET_GOLD(ch) >= buy_price(obj, shop_nr) || IS_GOD(ch)) && IS_CARRYING_N(ch) < CAN_CARRY_N(ch) && bought < buynum && IS_CARRYING_W(ch) + get_real_obj_weight(obj) <= CAN_CARRY_W(ch)) { bought++; /* Test if producing shop ! */ if (shop_producing(obj, shop_nr)) obj = read_object(GET_OBJ_RNUM(obj), REAL); else { obj_from_char(obj); SHOP_SORT(shop_nr)--; } obj = obj_to_char(obj, ch); goldamt += buy_price(obj, shop_nr); if (!IS_GOD(ch)) sub_gold(ch, buy_price(obj, shop_nr)); last_obj = obj; obj = get_purchase_obj(ch, arg, keeper, shop_nr, FALSE); if (!same_obj(obj, last_obj)) break; } if (bought < buynum) { if (!obj || !same_obj(last_obj, obj)) sprintf(buf, "%s I only have %d to sell you.", GET_NAME(ch), bought); else if (get_gold(ch) < buy_price(obj, shop_nr)) sprintf(buf, "%s You can only afford %d.", GET_NAME(ch), bought); else if (IS_CARRYING_N(ch) >= CAN_CARRY_N(ch)) sprintf(buf, "%s You can only hold %d.", GET_NAME(ch), bought); else if (IS_CARRYING_W(ch) + GET_OBJ_WEIGHT(obj) > CAN_CARRY_W(ch)) sprintf(buf, "%s You can only carry %d.", GET_NAME(ch), bought); else sprintf(buf, "%s Something screwy only gave you %d.", GET_NAME(ch), bought); do_tell(keeper, buf, cmd_tell, 0); } if (!IS_GOD(ch)) add_gold(keeper, goldamt); strcpy(tempstr, times_message(ch->first_carrying, 0, bought)); sprintf(buf, "$n buys %s.", tempstr); act(buf, FALSE, ch, obj, 0, TO_ROOM); sprintf(buf, shop_index[shop_nr].message_buy, GET_NAME(ch), goldamt); do_tell(keeper, buf, cmd_tell, 0); sprintf(buf, "You now have %s.\r\n", tempstr); send_to_char(buf, ch); if (SHOP_USES_BANK(shop_nr)) { if (get_gold(keeper) > MAX_OUTSIDE_BANK) { int amount = get_gold(keeper) - MAX_OUTSIDE_BANK; SHOP_BANK(shop_nr) += amount; sub_gold(keeper, amount); } } save_shop_nonnative(shop_nr, keeper); } OBJ_DATA *get_selling_obj(CHAR_DATA *ch, char *name, CHAR_DATA *keeper, int shop_nr, int msg) { OBJ_DATA *obj; char buf[MAX_STRING_LENGTH]; int result; if (!(obj = get_obj_in_list_vis_rev(ch, name, NULL, ch->last_carrying))) { if (msg) { sprintf(buf, shop_index[shop_nr].no_such_item2, GET_NAME(ch)); do_tell(keeper, buf, cmd_tell, 0); } return (NULL); } if ((result = trade_with(obj, shop_nr)) == OBJECT_OK) return (obj); switch (result) { case OBJECT_NOVAL: sprintf(buf, "%s You've got to be kidding, that thing is worthless!", GET_NAME(ch)); break; case OBJECT_NOTOK: sprintf(buf, shop_index[shop_nr].do_not_buy, GET_NAME(ch)); break; case OBJECT_DEAD: sprintf(buf, "%s %s", GET_NAME(ch), MSG_NO_USED_WANDSTAFF); break; default: log("SYSERR: Illegal return value of %d from trade_with() (%s)", result, __FILE__); /* Someone might rename it... */ sprintf(buf, "%s An error has occurred.", GET_NAME(ch)); break; } if (msg) do_tell(keeper, buf, cmd_tell, 0); return (NULL); } int sell_price(CHAR_DATA *ch, OBJ_DATA *obj, int shop_nr) { return (GET_OBJ_COST(obj) * SHOP_SELLPROFIT(shop_nr)); } /* * This function is a slight hack! To make sure that duplicate items are * only listed once on the "list", this function groups "identical" * objects together on the shopkeeper's inventory list. The hack involves * knowing how the list is put together, and manipulating the order of * the objects on the list. (But since most of DIKU is not encapsulated, * and information hiding is almost never used, it isn't that big a deal) -JF */ OBJ_DATA *slide_obj(OBJ_DATA *obj, CHAR_DATA *keeper, int shop_nr) { OBJ_DATA *loop; int temp; if (SHOP_SORT(shop_nr) < IS_CARRYING_N(keeper)) sort_keeper_objs(keeper, shop_nr); /* Extract the object if it is identical to one produced */ if (shop_producing(obj, shop_nr)) { temp = GET_OBJ_RNUM(obj); extract_obj(obj); return (&obj_proto[temp]); } SHOP_SORT(shop_nr)++; loop = keeper->first_carrying; obj = obj_to_char(obj, keeper); keeper->first_carrying = loop; while (loop) { if (same_obj(obj, loop)) { obj->next_content = loop->next_content; loop->next_content = obj; return (obj); } loop = loop->next_content; } keeper->first_carrying = obj; return (obj); } void sort_keeper_objs(CHAR_DATA *keeper, int shop_nr) { OBJ_DATA *list = NULL, *temp; while (SHOP_SORT(shop_nr) < IS_CARRYING_N(keeper)) { temp = keeper->first_carrying; obj_from_char(temp); temp->next_content = list; list = temp; } while (list) { temp = list; list = list->next_content; if (shop_producing(temp, shop_nr) && !get_obj_in_list_num(GET_OBJ_RNUM(temp), keeper->first_carrying)) { temp = obj_to_char(temp, keeper); SHOP_SORT(shop_nr)++; } else slide_obj(temp, keeper, shop_nr); } } void shopping_sell(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr) { OBJ_DATA *obj; char tempstr[MAX_INPUT_LENGTH], buf[MAX_STRING_LENGTH], name[MAX_INPUT_LENGTH]; int sellnum, sold = 0, goldamt = 0; if (!(is_ok(keeper, ch, shop_nr))) return; if ((sellnum = transaction_amt(arg)) < 0) { sprintf(buf, "%s A negative amount? Try buying something.", GET_NAME(ch)); do_tell(keeper, buf, cmd_tell, 0); return; } if (!*arg || !sellnum) { sprintf(buf, "%s What do you want to sell??", GET_NAME(ch)); do_tell(keeper, buf, cmd_tell, 0); return; } one_argument(arg, name); if (!(obj = get_selling_obj(ch, name, keeper, shop_nr, TRUE))) return; if (get_gold(keeper) + SHOP_BANK(shop_nr) < sell_price(ch, obj, shop_nr)) { sprintf(buf, shop_index[shop_nr].missing_cash1, GET_NAME(ch)); do_tell(keeper, buf, cmd_tell, 0); return; } while (obj && get_gold(keeper) + SHOP_BANK(shop_nr) >= sell_price(ch, obj, shop_nr) && sold < sellnum) { sold++; goldamt += sell_price(ch, obj, shop_nr); sub_gold(keeper, sell_price(ch, obj, shop_nr)); obj_from_char(obj); slide_obj(obj, keeper, shop_nr); /* Seems we don't use return value. */ obj = get_selling_obj(ch, name, keeper, shop_nr, FALSE); } if (sold < sellnum) { if (!obj) sprintf(buf, "%s You only have %d of those.", GET_NAME(ch), sold); else if (get_gold(keeper) + SHOP_BANK(shop_nr) < sell_price(ch, obj, shop_nr)) sprintf(buf, "%s I can only afford to buy %d of those.", GET_NAME(ch), sold); else sprintf(buf, "%s Something really screwy made me buy %d.", GET_NAME(ch), sold); do_tell(keeper, buf, cmd_tell, 0); } add_gold(ch, goldamt); strcpy(tempstr, times_message(0, name, sold)); sprintf(buf, "$n sells %s.", tempstr); act(buf, FALSE, ch, obj, 0, TO_ROOM); sprintf(buf, shop_index[shop_nr].message_sell, GET_NAME(ch), goldamt); do_tell(keeper, buf, cmd_tell, 0); sprintf(buf, "The shopkeeper now has %s.\r\n", tempstr); send_to_char(buf, ch); if (get_gold(keeper) < MIN_OUTSIDE_BANK) { goldamt = MIN(MAX_OUTSIDE_BANK - get_gold(keeper), SHOP_BANK(shop_nr)); SHOP_BANK(shop_nr) -= goldamt; add_gold(keeper, goldamt); } save_shop_nonnative(shop_nr, keeper); } void shopping_value(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr) { OBJ_DATA *obj; char buf[MAX_STRING_LENGTH], name[MAX_INPUT_LENGTH]; if (!is_ok(keeper, ch, shop_nr)) return; if (!*arg) { sprintf(buf, "%s What do you want me to evaluate??", GET_NAME(ch)); do_tell(keeper, buf, cmd_tell, 0); return; } one_argument(arg, name); if (!(obj = get_selling_obj(ch, name, keeper, shop_nr, TRUE))) return; sprintf(buf, "%s I'll give you %d gold coins for that!", GET_NAME(ch), sell_price(ch, obj, shop_nr)); do_tell(keeper, buf, cmd_tell, 0); return; } char *list_object(OBJ_DATA *obj, int cnt, int index, int shop_nr) { static char buf[256]; char buf2[300], buf3[200]; if (shop_producing(obj, shop_nr)) strcpy(buf2, "Unlimited "); else sprintf(buf2, "%5d ", cnt); sprintf(buf, " %2d) %s", index, buf2); /* Compile object name and information */ strcpy(buf3, obj->short_description); if (GET_OBJ_TYPE(obj) == ITEM_DRINKCON && GET_OBJ_VAL(obj, 1)) sprintf(END_OF(buf3), " of %s", drinks[GET_OBJ_VAL(obj, 2)]); /* FUTURE: */ /* Add glow/hum/etc */ if (GET_OBJ_TYPE(obj) == ITEM_WAND || GET_OBJ_TYPE(obj) == ITEM_STAFF) if (GET_OBJ_VAL(obj, 2) < GET_OBJ_VAL(obj, 1)) strcat(buf3, " (partially used)"); sprintf(buf2, "%-48s %6d\r\n", buf3, buy_price(obj, shop_nr)); strcat(buf, CAP(buf2)); return (buf); } void shopping_list(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr) { OBJ_DATA *obj, *last_obj = NULL; char buf[MAX_STRING_LENGTH], name[MAX_INPUT_LENGTH]; int cnt = 0, index = 0, found = FALSE; /* cnt is the number of that particular object available */ if (!is_ok(keeper, ch, shop_nr)) return; if (SHOP_SORT(shop_nr) < IS_CARRYING_N(keeper)) sort_keeper_objs(keeper, shop_nr); one_argument(arg, name); strcpy(buf, " ## Available Item Cost\r\n"); strcat(buf, "-------------------------------------------------------------------------\r\n"); if (keeper->first_carrying) { for (obj = keeper->first_carrying; obj; obj = obj->next_content) { if (CAN_SEE_OBJ(ch, obj) && GET_OBJ_COST(obj) > 0 && GET_OBJ_TYPE(obj) != ITEM_MONEY ) { if (!last_obj) { last_obj = obj; cnt = 1; } else if (same_obj(last_obj, obj)) cnt++; else { index++; if (!*name || isname(name, last_obj->name)) { strcat(buf, list_object(last_obj, cnt, index, shop_nr)); found = TRUE; } cnt = 1; last_obj = obj; } } } } index++; if (!last_obj) /* we actually have nothing in our list for sale, period */ strcpy(buf, "Currently, there is nothing for sale.\r\n"); else if (!*name || isname(name, last_obj->name)) /* show last obj */ strcat(buf, list_object(last_obj, cnt, index, shop_nr)); else if (!found) /* nothing the char was looking for was found */ strcpy(buf, "Presently, none of those are for sale.\r\n"); page_string(ch->desc, buf, TRUE); } int ok_shop_room(int shop_nr, ROOM_DATA *room) { int index; for (index = 0; SHOP_ROOM(shop_nr, index) != NULL; index++) if (SHOP_ROOM(shop_nr, index) == room) return (TRUE); return (FALSE); } SPECIAL(shop_keeper) { CHAR_DATA *keeper = (CHAR_DATA *) me; char argm[MAX_INPUT_LENGTH]; int shop_nr; for (shop_nr = 0; shop_nr <= top_shop; shop_nr++) { if (SHOP_KEEPER(shop_nr) == keeper->nr) break; } if (shop_nr > top_shop) return (FALSE); if (SHOP_FUNC(shop_nr)) /* Check secondary function */ { if ((SHOP_FUNC(shop_nr)) (ch, me, cmd, argument)) return (TRUE); } if (keeper == ch) { if (cmd) SHOP_SORT(shop_nr) = 0; /* Safety in case "drop all" */ return (FALSE); } if (!ok_shop_room(shop_nr, ch->in_room)) return (0); if (!AWAKE(keeper)) return (FALSE); if (CMD_IS("steal")) { sprintf(argm, "$N shouts '%s'", MSG_NO_STEAL_HERE); do_action(keeper, GET_NAME(ch), cmd_slap, 0); act(argm, FALSE, ch, 0, keeper, TO_CHAR); return (TRUE); } if (CMD_IS("buy")) { shopping_buy(argument, ch, keeper, shop_nr); return (TRUE); } else if (CMD_IS("sell")) { shopping_sell(argument, ch, keeper, shop_nr); return (TRUE); } else if (CMD_IS("value")) { shopping_value(argument, ch, keeper, shop_nr); return (TRUE); } else if (CMD_IS("list")) { shopping_list(argument, ch, keeper, shop_nr); return (TRUE); } return (FALSE); } int ok_damage_shopkeeper(CHAR_DATA *ch, CHAR_DATA *victim) { char buf[MAX_INPUT_LENGTH]; int index; /* Prevent "invincible" shopkeepers if they're charmed. */ if (AFF_FLAGGED(victim, AFF_CHARM)) return (TRUE); if (IS_MOB(victim) && mob_index[GET_MOB_RNUM(victim)].func == shop_keeper) for (index = 0; index <= top_shop; index++) if (GET_MOB_RNUM(victim) == SHOP_KEEPER(index) && !SHOP_KILL_CHARS(index)) { do_action(victim, GET_NAME(ch), cmd_slap, 0); sprintf(buf, "%s %s", GET_NAME(ch), MSG_CANT_KILL_KEEPER); do_tell(victim, buf, cmd_tell, 0); return (FALSE); } return (TRUE); } /* val == obj_vnum and obj_rnum (?) */ int add_to_list(SHOP_BUY_DATA *list, int type, int *len, int *val) { if (*val != NOTHING) { if (*len < MAX_SHOP_OBJ) { if (type == LIST_PRODUCE) *val = real_object(*val); if (*val != NOTHING) { BUY_TYPE(list[*len]) = *val; BUY_WORD(list[(*len)++]) = 0; } else *val = NOTHING; return (FALSE); } else return (TRUE); } return (FALSE); } int end_read_list(SHOP_BUY_DATA *list, int len, int error) { if (error) log("SYSERR: Raise MAX_SHOP_OBJ constant in shop.h to %d", len + error); BUY_WORD(list[len]) = NULL; BUY_TYPE(list[len++]) = NOTHING; return (len); } void read_line(FILE *shop_f, const char *string, void *data) { if (!get_line(shop_f, buf) || !sscanf(buf, string, data)) { log("SYSERR: Error in shop #%d\n", SHOP_NUM(top_shop)); exit(1); } } int read_list(FILE *shop_f, SHOP_BUY_DATA *list, int new_format, int max, int type) { int count, temp, len = 0, error = 0; if (new_format) { do { read_line(shop_f, "%d", &temp); error += add_to_list(list, type, &len, &temp); } while (temp >= 0); } else for (count = 0; count < max; count++) { read_line(shop_f, "%d", &temp); error += add_to_list(list, type, &len, &temp); } return (end_read_list(list, len, error)); } /* END_OF inefficient. */ int read_type_list(FILE *shop_f, SHOP_BUY_DATA *list, int new_format, int max) { int index, num, len = 0, error = 0; char *ptr; if (!new_format) return (read_list(shop_f, list, 0, max, LIST_TRADE)); do { fgets(buf, MAX_STRING_LENGTH - 1, shop_f); if ((ptr = strchr(buf, ';')) != NULL) *ptr = '\0'; else *(END_OF(buf) - 1) = '\0'; for (index = 0, num = NOTHING; *item_types[index] != '\n'; index++) if (!strn_cmp(item_types[index], buf, strlen(item_types[index]))) { num = index; strcpy(buf, buf + strlen(item_types[index])); break; } ptr = buf; if (num == NOTHING) { sscanf(buf, "%d", &num); while (!isdigit(*ptr)) ptr++; while (isdigit(*ptr)) ptr++; } while (isspace(*ptr)) ptr++; while (isspace(*(END_OF(ptr) - 1))) *(END_OF(ptr) - 1) = '\0'; error += add_to_list(list, LIST_TRADE, &len, &num); if (*ptr) BUY_WORD(list[len - 1]) = str_dup(ptr); } while (num >= 0); return (end_read_list(list, len, error)); } char *read_shop_message(int mnum, room_vnum shr, FILE *shop_f, const char *why) { int cht, ss = 0, ds = 0, err = 0; char *tbuf; if (!(tbuf = fread_string(shop_f, why))) return (NULL); for (cht = 0; tbuf[cht]; cht++) { if (tbuf[cht] != '%') continue; if (tbuf[cht + 1] == 's') ss++; else if (tbuf[cht + 1] == 'd' && (mnum == 5 || mnum == 6)) { if (ss == 0) { log("SYSERR: Shop #%d has %%d before %%s, message #%d.", shr, mnum); err++; } ds++; } else if (tbuf[cht + 1] != '%') { log("SYSERR: Shop #%d has invalid format '%%%c' in message #%d.", shr, tbuf[cht + 1], mnum); err++; } } if (ss > 1 || ds > 1) { log("SYSERR: Shop #%d has too many specifiers for message #%d. %%s=%d %%d=%d", shr, mnum, ss, ds); err++; } if (err) { free(tbuf); return (NULL); } return (tbuf); } void boot_the_shops(FILE *shop_f, char *filename, int rec_count) { SHOP_BUY_DATA list[MAX_SHOP_OBJ + 1]; char *buf, buf2[MAX_INPUT_LENGTH]; int temp, count, new_format = FALSE; int done = FALSE; sprintf(buf2, "beginning of shop file %s", filename); while (!done) { buf = fread_string(shop_f, buf2); if (*buf == '#') /* New shop */ { sscanf(buf, "#%d\n", &temp); sprintf(buf2, "shop #%d in shop file %s", temp, filename); free(buf); /* Plug memory leak! */ top_shop++; if (!top_shop) CREATE(shop_index, SHOP_DATA, rec_count); SHOP_NUM(top_shop) = temp; temp = read_list(shop_f, list, new_format, MAX_PROD, LIST_PRODUCE); CREATE(shop_index[top_shop].producing, obj_vnum, temp); for (count = 0; count < temp; count++) SHOP_PRODUCT(top_shop, count) = BUY_TYPE(list[count]); read_line(shop_f, "%f", &SHOP_BUYPROFIT(top_shop)); read_line(shop_f, "%f", &SHOP_SELLPROFIT(top_shop)); temp = read_type_list(shop_f, list, new_format, MAX_TRADE); CREATE(shop_index[top_shop].type, SHOP_BUY_DATA, temp); for (count = 0; count < temp; count++) { SHOP_BUYTYPE(top_shop, count) = BUY_TYPE(list[count]); SHOP_BUYWORD(top_shop, count) = BUY_WORD(list[count]); } shop_index[top_shop].no_such_item1 = read_shop_message(0, SHOP_NUM(top_shop), shop_f, buf2); shop_index[top_shop].no_such_item2 = read_shop_message(1, SHOP_NUM(top_shop), shop_f, buf2); shop_index[top_shop].do_not_buy = read_shop_message(2, SHOP_NUM(top_shop), shop_f, buf2); shop_index[top_shop].missing_cash1 = read_shop_message(3, SHOP_NUM(top_shop), shop_f, buf2); shop_index[top_shop].missing_cash2 = read_shop_message(4, SHOP_NUM(top_shop), shop_f, buf2); shop_index[top_shop].message_buy = read_shop_message(5, SHOP_NUM(top_shop), shop_f, buf2); shop_index[top_shop].message_sell = read_shop_message(6, SHOP_NUM(top_shop), shop_f, buf2); read_line(shop_f, "%d", &SHOP_BROKE_TEMPER(top_shop)); read_line(shop_f, "%d", &SHOP_BITVECTOR(top_shop)); read_line(shop_f, "%hd", &SHOP_KEEPER(top_shop)); SHOP_KEEPER(top_shop) = real_mobile(SHOP_KEEPER(top_shop)); read_line(shop_f, "%d", &SHOP_TRADE_WITH(top_shop)); temp = read_list(shop_f, list, new_format, 1, LIST_ROOM); CREATE(shop_index[top_shop].in_room, ROOM_DATA *, temp); for (count = 0; count < temp; count++) SHOP_ROOM(top_shop, count) = get_room(BUY_TYPE(list[count])); read_line(shop_f, "%d", &SHOP_OPEN1(top_shop)); read_line(shop_f, "%d", &SHOP_CLOSE1(top_shop)); read_line(shop_f, "%d", &SHOP_OPEN2(top_shop)); read_line(shop_f, "%d", &SHOP_CLOSE2(top_shop)); SHOP_BANK(top_shop) = 0; SHOP_SORT(top_shop) = 0; SHOP_FUNC(top_shop) = NULL; } else { if (*buf == '$') /* EOF */ done = TRUE; else if (strstr(buf, VERSION3_TAG)) /* New format marker */ new_format = TRUE; free(buf); /* Plug memory leak! */ } } } void assign_the_shopkeepers(void) { int index; cmd_say = find_command("say"); cmd_tell = find_command("tell"); cmd_emote = find_command("emote"); cmd_slap = find_command("slap"); cmd_puke = find_command("puke"); for (index = 0; index <= top_shop; index++) { if (SHOP_KEEPER(index) == NOBODY) continue; if (mob_index[SHOP_KEEPER(index)].func) SHOP_FUNC(index) = mob_index[SHOP_KEEPER(index)].func; mob_index[SHOP_KEEPER(index)].func = shop_keeper; } } char *customer_string(int shop_nr, int detailed) { int index, cnt = 1; static char buf[256]; *buf = 0; for (index = 0; *trade_letters[index] != '\n'; index++, cnt *= 2) if (!(SHOP_TRADE_WITH(shop_nr) & cnt)) { if (detailed) { if (*buf) strcat(buf, ", "); strcat(buf, trade_letters[index]); } else sprintf(END_OF(buf), "%c", *trade_letters[index]); } else if (!detailed) strcat(buf, "_"); return (buf); } /* END_OF inefficient */ void list_all_shops(CHAR_DATA *ch) { int shop_nr; *buf = '\0'; for (shop_nr = 0; shop_nr <= top_shop; shop_nr++) { if (!(shop_nr % (PAGE_LENGTH - 2))) { strcat(buf, " ## Virtual Where Keeper Buy Sell Customers\r\n"); strcat(buf, "---------------------------------------------------------\r\n"); } sprintf(buf2, "%3d %6d %6d ", shop_nr + 1, SHOP_NUM(shop_nr), SHOP_ROOM(shop_nr, 0)->number); if (SHOP_KEEPER(shop_nr) < 0) strcpy(buf1, "<NONE>"); else sprintf(buf1, "%6d", mob_index[SHOP_KEEPER(shop_nr)].vnum); sprintf(END_OF(buf2), "%s %3.2f %3.2f ", buf1, SHOP_SELLPROFIT(shop_nr), SHOP_BUYPROFIT(shop_nr)); strcat(buf2, customer_string(shop_nr, FALSE)); sprintf(END_OF(buf), "%s\r\n", buf2); } page_string(ch->desc, buf, TRUE); } void handle_detailed_list(char *buf, char *buf1, CHAR_DATA *ch) { if (strlen(buf1) + strlen(buf) < 78 || strlen(buf) < 20) strcat(buf, buf1); else { strcat(buf, "\r\n"); send_to_char(buf, ch); sprintf(buf, " %s", buf1); } } void list_detailed_shop(CHAR_DATA *ch, int shop_nr) { OBJ_DATA *obj; CHAR_DATA *k; int index; ROOM_DATA *temp; sprintf(buf, "Vnum: [%5d], Rnum: [%5d]\r\n", SHOP_NUM(shop_nr), shop_nr + 1); send_to_char(buf, ch); strcpy(buf, "Rooms: "); for (index = 0; SHOP_ROOM(shop_nr, index) != NULL; index++) { if (index) strcat(buf, ", "); if ((temp = SHOP_ROOM(shop_nr, index)) != NULL) sprintf(buf1, "%s (#%d)", temp->name, temp->number); else sprintf(buf1, "<UNKNOWN> (#%d)", SHOP_ROOM(shop_nr, index)); handle_detailed_list(buf, buf1, ch); } if (!index) send_to_char("Rooms: None!\r\n", ch); else { strcat(buf, "\r\n"); send_to_char(buf, ch); } strcpy(buf, "Shopkeeper: "); if (SHOP_KEEPER(shop_nr) >= 0) { sprintf(END_OF(buf), "%s (#%d), Special Function: %s\r\n", GET_NAME(&mob_proto[SHOP_KEEPER(shop_nr)]), mob_index[SHOP_KEEPER(shop_nr)].vnum, YESNO(SHOP_FUNC(shop_nr))); if ((k = get_char_num(SHOP_KEEPER(shop_nr)))) { send_to_char(buf, ch); sprintf(buf, "Coins: [%9d], Bank: [%9d] (Total: %d)\r\n", get_gold(k), SHOP_BANK(shop_nr), get_gold(k) + SHOP_BANK(shop_nr)); } } else strcat(buf, "<NONE>\r\n"); send_to_char(buf, ch); strcpy(buf1, customer_string(shop_nr, TRUE)); sprintf(buf, "Customers: %s\r\n", (*buf1) ? buf1 : "None"); send_to_char(buf, ch); strcpy(buf, "Produces: "); for (index = 0; SHOP_PRODUCT(shop_nr, index) != NOTHING; index++) { obj = &obj_proto[SHOP_PRODUCT(shop_nr, index)]; if (index) strcat(buf, ", "); sprintf(buf1, "%s (#%d)", obj->short_description, obj_index[SHOP_PRODUCT(shop_nr, index)].vnum); handle_detailed_list(buf, buf1, ch); } if (!index) send_to_char("Produces: Nothing!\r\n", ch); else { strcat(buf, "\r\n"); send_to_char(buf, ch); } strcpy(buf, "Buys: "); for (index = 0; SHOP_BUYTYPE(shop_nr, index) != NOTHING; index++) { if (index) strcat(buf, ", "); sprintf(buf1, "%s (#%d) ", item_types[SHOP_BUYTYPE(shop_nr, index)], SHOP_BUYTYPE(shop_nr, index)); if (SHOP_BUYWORD(shop_nr, index)) sprintf(END_OF(buf1), "[%s]", SHOP_BUYWORD(shop_nr, index)); else strcat(buf1, "[all]"); handle_detailed_list(buf, buf1, ch); } if (!index) send_to_char("Buys: Nothing!\r\n", ch); else { strcat(buf, "\r\n"); send_to_char(buf, ch); } sprintf(buf, "Buy at: [%4.2f], Sell at: [%4.2f], Open: [%d-%d, %d-%d]%s", SHOP_SELLPROFIT(shop_nr), SHOP_BUYPROFIT(shop_nr), SHOP_OPEN1(shop_nr), SHOP_CLOSE1(shop_nr), SHOP_OPEN2(shop_nr), SHOP_CLOSE2(shop_nr), "\r\n"); send_to_char(buf, ch); sprintbit(SHOP_BITVECTOR(shop_nr), shop_bits, buf1); sprintf(buf, "Bits: %s\r\n", buf1); send_to_char(buf, ch); } void show_shops(CHAR_DATA *ch, char *arg) { int shop_nr; if (!*arg) list_all_shops(ch); else { if (!str_cmp(arg, ".")) { for (shop_nr = 0; shop_nr <= top_shop; shop_nr++) if (ok_shop_room(shop_nr, ch->in_room)) break; if (shop_nr > top_shop) { send_to_char("This isn't a shop!\r\n", ch); return; } } else if (is_number(arg)) shop_nr = atoi(arg) - 1; else shop_nr = -1; if (shop_nr < 0 || shop_nr > top_shop) { send_to_char("Illegal shop number.\r\n", ch); return; } list_detailed_shop(ch, shop_nr); } }