diff --git a/lib/world/mob/1.mob b/lib/world/mob/1.mob index f9db6c5..9fb7b15 100644 --- a/lib/world/mob/1.mob +++ b/lib/world/mob/1.mob @@ -16,6 +16,17 @@ Str: 16 Dex: 14 Con: 14 E +L 17 127 1 +L 16 117 1 +L 15 117 1 +L 11 111 1 +L 10 107 1 +L 9 124 1 +L 8 115 1 +L 7 108 1 +L 6 110 1 +L 5 131 1 +L 3 118 1 #101 slim lanky human soldier guard~ a slim, lanky human soldier~ diff --git a/lib/world/obj/1.obj b/lib/world/obj/1.obj index 2e0ae89..02e9a6d 100644 --- a/lib/world/obj/1.obj +++ b/lib/world/obj/1.obj @@ -3,7 +3,7 @@ padded armor~ some padded armor~ Some padded armor has been discarded here.~ ~ -9 0 0 0 0 ad 0 0 0 0 0 0 0 +9 0 0 0 0 ae 0 0 0 0 0 0 0 1 1 0 0 10 4 0 0 0 #101 @@ -11,7 +11,7 @@ erdlu leather armor~ some erdlu leather armor~ An unfinished object is lying here.~ ~ -9 0 0 0 0 ad 0 0 0 0 0 0 0 +9 0 0 0 0 ae 0 0 0 0 0 0 0 1 1 0 0 5 5 0 0 0 #102 @@ -19,7 +19,7 @@ studded leather jacket~ a studded leather jacket~ A jacket made from studded leather lies here.~ ~ -9 0 0 0 0 ad 0 0 0 0 0 0 0 +9 0 0 0 0 ae 0 0 0 0 0 0 0 2 1 0 0 13 10 0 0 0 #103 @@ -27,7 +27,7 @@ bone chitin armor~ some bone and chitin armor~ A piece of armor made from bone and chitin lies here.~ ~ -9 0 0 0 0 ad 0 0 0 0 0 0 0 +9 0 0 0 0 ae 0 0 0 0 0 0 0 1 1 0 0 20 12 0 0 0 #104 @@ -35,7 +35,7 @@ pair padded sleeves~ a pair of padded sleeves~ A pair of padded cloth sleeves lie here abandoned.~ ~ -9 0 0 0 0 ai 0 0 0 0 0 0 0 +9 0 0 0 0 aj 0 0 0 0 0 0 0 1 1 0 0 5 10 0 0 0 #105 @@ -43,7 +43,7 @@ pair padded leggings~ a pair of padded leggings~ A pair of padded leggings lie here in the dust.~ ~ -9 0 0 0 0 af 0 0 0 0 0 0 0 +9 0 0 0 0 ag 0 0 0 0 0 0 0 1 1 0 0 5 10 0 0 0 #106 @@ -51,7 +51,7 @@ pair cloth gloves~ a pair of cloth gloves~ A pair of yellowed cloth gloves lie here.~ ~ -11 0 0 0 0 ah 0 0 0 0 0 0 0 +11 0 0 0 0 ai 0 0 0 0 0 0 0 0 0 0 0 2 10 0 0 0 #107 @@ -59,7 +59,7 @@ pair leather gloves~ a pair of leather gloves~ A pair of thick leather gloves have been left here.~ ~ -9 0 0 0 0 ah 0 0 0 0 0 0 0 +9 0 0 0 0 ai 0 0 0 0 0 0 0 1 1 0 0 6 10 0 0 0 #108 @@ -67,7 +67,7 @@ bone helmet~ a bone helmet~ A helmet made of bone has been discarded here.~ ~ -9 0 0 0 0 ae 0 0 0 0 0 0 0 +9 0 0 0 0 af 0 0 0 0 0 0 0 1 1 0 0 12 18 0 0 0 #109 @@ -75,7 +75,7 @@ padded cloth helmet~ a padded cloth helmet~ A padded cloth helmet has been left here.~ ~ -9 0 0 0 0 ae 0 0 0 0 0 0 0 +9 0 0 0 0 af 0 0 0 0 0 0 0 1 1 0 0 4 11 0 0 0 #110 @@ -83,7 +83,7 @@ braxat hide jacket~ a braxat hide jacket~ A thick jacket made of braxat hide has been abandoned here.~ ~ -9 0 0 0 0 ad 0 0 0 0 0 0 0 +9 0 0 0 0 ae 0 0 0 0 0 0 0 3 3 0 1 30 250 0 0 0 #111 @@ -91,7 +91,7 @@ pair thick leather sleeves~ a pair of thick leather sleeves~ Cured and stitched tight, a pair of thick leather sleeves are here.~ ~ -9 0 0 0 0 ai 0 0 0 0 0 0 0 +9 0 0 0 0 aj 0 0 0 0 0 0 0 1 1 0 0 14 30 0 0 0 #112 @@ -99,7 +99,7 @@ pair sandals~ a pair of sandals~ Some cheap looking sandals have been left in the dust here.~ ~ -11 0 0 0 0 ag 0 0 0 0 0 0 0 +11 0 0 0 0 ah 0 0 0 0 0 0 0 0 0 0 0 1 8 0 0 0 #113 @@ -107,7 +107,7 @@ loincloth~ a loincloth~ A scrap of cloth with a string attached has been left here.~ ~ -11 0 0 0 0 al 0 0 0 0 0 0 0 +11 0 0 0 0 am 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 #114 @@ -115,7 +115,7 @@ erdlu scale shield~ an erdlu scale shield~ A shield made from erdlu scales lies here, collecting dust.~ ~ -9 0 0 0 0 aj 0 0 0 0 0 0 0 +9 0 0 0 0 ak 0 0 0 0 0 0 0 0 0 0 0 5 50 0 0 0 #115 @@ -123,7 +123,7 @@ pair thick leather leggings~ a pair of thick leather leggings~ A pair of leggings made from thick, dark leather are here.~ ~ -9 0 0 0 0 af 0 0 0 0 0 0 0 +9 0 0 0 0 ag 0 0 0 0 0 0 0 1 1 0 0 12 50 0 0 0 #116 @@ -131,7 +131,7 @@ hide wrist wrap wrist-wrap~ a hide wrist-wrap~ A simple hide wrist-wrap gathers dust and sand here.~ ~ -9 0 0 0 0 am 0 0 0 0 0 0 0 +9 0 0 0 0 an 0 0 0 0 0 0 0 1 1 0 0 2 25 0 0 0 #117 @@ -139,7 +139,7 @@ studded hide wrist wrap wrist-wrap~ a studded hide wrist-wrap~ A hide wrist-wrap with studded bone bits has been left here.~ ~ -9 0 0 0 0 am 0 0 0 0 0 0 0 +9 0 0 0 0 an 0 0 0 0 0 0 0 1 1 0 0 8 40 0 0 0 #118 @@ -155,7 +155,7 @@ pair padded shoes~ a pair of padded shoes~ An assuming pair of padded cloth shoes are here.~ ~ -9 0 0 0 0 ag 0 0 0 0 0 0 0 +9 0 0 0 0 ah 0 0 0 0 0 0 0 1 1 0 0 8 25 0 0 0 #120 @@ -163,7 +163,7 @@ black belt~ a black belt~ A belt made of black cloth lies here.~ ~ -15 0 0 0 0 al 0 0 0 0 0 0 0 +15 0 0 0 0 am 0 0 0 0 0 0 0 10 0 0 0 1 25 0 0 0 #121 @@ -187,7 +187,7 @@ pair boots~ a pair of boots~ A pair of simple leather boots are here.~ ~ -9 0 0 0 0 ag 0 0 0 0 0 0 0 +9 0 0 0 0 ah 0 0 0 0 0 0 0 1 1 0 0 1 25 0 0 0 #124 @@ -195,7 +195,7 @@ pair thick leather boots~ a pair of thick leather boots~ Boots made of thick leather have been left here.~ ~ -9 0 0 0 0 ag 0 0 0 0 0 0 0 +9 0 0 0 0 ah 0 0 0 0 0 0 0 2 2 0 0 5 50 0 0 0 #125 @@ -203,7 +203,7 @@ bone shortsword sword~ a bone shortsword~ Made of bone, a shortsword has been left here.~ ~ -5 0 0 0 0 ano 0 0 0 0 0 0 0 +5 0 0 0 0 aop 0 0 0 0 0 0 0 1 6 11 0 5 50 0 0 0 #126 @@ -211,7 +211,7 @@ bone dagger~ a bone dagger~ A dagger made of bone lies here abandoned.~ ~ -5 0 0 0 0 ano 0 0 0 0 0 0 0 +5 0 0 0 0 aop 0 0 0 0 0 0 0 1 4 11 0 2 25 0 0 0 #127 @@ -219,7 +219,7 @@ bone longsword sword~ a bone longsword~ Long and slender, a sword lies here.~ ~ -5 0 0 0 0 an 0 0 0 0 0 0 0 +5 0 0 0 0 ao 0 0 0 0 0 0 0 1 8 3 0 8 100 0 0 0 #128 @@ -227,7 +227,7 @@ bone club~ a bone club~ Made of bone, a club is lying here.~ ~ -5 0 0 0 0 ano 0 0 0 0 0 0 0 +5 0 0 0 0 aop 0 0 0 0 0 0 0 1 4 5 0 5 50 0 0 0 #129 @@ -235,7 +235,7 @@ bone javelin~ a bone javelin~ A long, bone javelin has been left here.~ ~ -5 0 0 0 0 an 0 0 0 0 0 0 0 +5 0 0 0 0 ao 0 0 0 0 0 0 0 1 6 11 0 5 50 0 0 0 #130 @@ -243,7 +243,7 @@ bone spear~ a bone spear~ A long piece of bones with a sharpened edge has been left here.~ ~ -5 0 0 0 0 an 0 0 0 0 0 0 0 +5 0 0 0 0 ao 0 0 0 0 0 0 0 1 6 11 0 3 50 0 0 0 #131 diff --git a/lib/world/zon/1.zon b/lib/world/zon/1.zon index 623a9f4..b08214b 100644 --- a/lib/world/zon/1.zon +++ b/lib/world/zon/1.zon @@ -3,15 +3,5 @@ None.~ City of Tyr~ 100 199 30 2 M 0 100 1 100 (the tall, burly human soldier) -E 1 102 1 6 (a studded leather jacket) -E 1 115 1 8 (a pair of thick leather leggings) -E 1 111 1 11 (a pair of thick leather sleeves) -E 1 108 1 7 (a bone helmet) -E 1 117 2 16 (a studded hide wrist-wrap) -E 1 118 1 3 (a padded neckguard) -E 1 117 2 15 (a studded hide wrist-wrap) -E 1 107 1 10 (a pair of leather gloves) -E 1 124 1 9 (a pair of thick leather boots) -E 1 127 1 17 (a bone longsword) S $ diff --git a/src/act.h b/src/act.h index 3080d24..2f974ec 100644 --- a/src/act.h +++ b/src/act.h @@ -318,6 +318,7 @@ ACMD(do_goto); ACMD(do_invis); ACMD(do_links); ACMD(do_load); +ACMD(do_msave); ACMD(do_oset); ACMD(do_peace); ACMD(do_plist); diff --git a/src/act.wizard.c b/src/act.wizard.c index 894b050..93df109 100644 --- a/src/act.wizard.c +++ b/src/act.wizard.c @@ -26,6 +26,7 @@ #include "act.h" #include "genzon.h" /* for real_zone_by_thing */ #include "class.h" +#include "genmob.h" #include "genolc.h" #include "genobj.h" #include "fight.h" @@ -5733,4 +5734,145 @@ ACMD(do_acaudit) #undef APPEND_FMT } +/* ====== Builder snapshot: save a staged mob's gear as its prototype loadout ====== */ +/* Put these helpers near the top of act.wizard.c (or a shared .c) */ +struct inv_count { obj_vnum vnum; int qty; struct inv_count *next; }; +static void inv_add(struct inv_count **head, obj_vnum v, int q) { + struct inv_count *p; + if (q < 1) q = 1; + for (p = *head; p; p = p->next) + if (p->vnum == v) { p->qty += q; return; } + CREATE(p, struct inv_count, 1); + p->vnum = v; p->qty = q; p->next = *head; *head = p; +} + +static void inv_free_all(struct inv_count **head) { + struct inv_count *n, *p = *head; + while (p) { n = p->next; free(p); p = n; } + *head = NULL; +} + +/* Add ACMD prototype to interpreter.h: ACMD(do_msave); */ + +ACMD(do_msave) +{ + char a1[MAX_INPUT_LENGTH], a2[MAX_INPUT_LENGTH]; + char target[MAX_INPUT_LENGTH] = {0}, flags[MAX_INPUT_LENGTH] = {0}; + struct char_data *vict = NULL, *tmp = NULL; + mob_rnum rnum; + int include_inv = 0; /* -all */ + int clear_first = 1; /* default replace; -append flips this to 0 */ + int equips_added = 0, inv_entries = 0; + struct inv_count *inv = NULL; + int pos; + struct obj_data *o; + + two_arguments(argument, a1, a2); + if (*a1 && *a1 == '-') { + /* user wrote: msave -flags */ + strcpy(flags, a1); + strcpy(target, a2); + } else { + /* user wrote: msave [-flags] */ + strcpy(target, a1); + strcpy(flags, a2); + } + + /* Parse flags (space-separated, any order) */ + if (*flags) { + char buf[MAX_INPUT_LENGTH], *p = flags; + while (*p) { + p = one_argument(p, buf); + if (!*buf) break; + if (!str_cmp(buf, "-all")) include_inv = 1; + else if (!str_cmp(buf, "-append")) clear_first = 0; + else if (!str_cmp(buf, "-clear")) clear_first = 1; + else { + send_to_char(ch, "Unknown flag '%s'. Try -all, -append, or -clear.\r\n", buf); + return; + } + } + } + + /* Find target mob in the room */ + if (*target) + vict = get_char_vis(ch, target, NULL, FIND_CHAR_ROOM); + else { + /* No name: pick the first NPC only if exactly one exists */ + for (tmp = world[IN_ROOM(ch)].people; tmp; tmp = tmp->next_in_room) { + if (IS_NPC(tmp)) { + if (vict) { vict = NULL; break; } /* more than one — force explicit name */ + vict = tmp; + } + } + } + + if (!vict || !IS_NPC(vict)) { + send_to_char(ch, "Target an NPC in this room: msave [-all] [-append|-clear]\r\n"); + return; + } + + /* Resolve prototype and permission to edit its zone */ + rnum = GET_MOB_RNUM(vict); + if (rnum < 0) { + send_to_char(ch, "I can’t resolve that mob's prototype.\r\n"); + return; + } + +#ifdef CAN_EDIT_ZONE + if (!can_edit_zone(ch, real_zone_by_thing(GET_MOB_VNUM(vict)))) { + send_to_char(ch, "You don’t have permission to modify that mob’s zone.\r\n"); + return; + } +#endif + + /* Build the new loadout into the PROTOTYPE */ + if (clear_first) + loadout_free_list(&mob_proto[rnum].proto_loadout); + + /* Capture equipment: one entry per worn slot */ + for (pos = 0; pos < NUM_WEARS; pos++) { + o = GET_EQ(vict, pos); + if (!o) continue; + if (GET_OBJ_VNUM(o) <= 0) continue; + loadout_add_entry(&mob_proto[rnum].proto_loadout, GET_OBJ_VNUM(o), (sh_int)pos, 1); + equips_added++; + } + + /* Capture inventory (compressed by vnum) if requested */ + if (include_inv) { + for (o = vict->carrying; o; o = o->next_content) { + if (GET_OBJ_VNUM(o) <= 0) continue; + inv_add(&inv, GET_OBJ_VNUM(o), 1); + } + { + struct inv_count *p; + for (p = inv; p; p = p->next) { + loadout_add_entry(&mob_proto[rnum].proto_loadout, p->vnum, -1, MAX(1, p->qty)); + inv_entries++; + } + } + } + + /* Persist to disk: save the zone owning this mob vnum */ + { + zone_rnum zr = real_zone_by_thing(GET_MOB_VNUM(vict)); + if (zr == NOWHERE) { + mudlog(CMP, MAX(LVL_GOD, GET_INVIS_LEV(ch)), TRUE, + "msave: could not resolve zone for mob %d", GET_MOB_VNUM(vict)); + send_to_char(ch, "Saved in memory, but couldn’t resolve zone to write disk.\r\n"); + } else { + save_mobiles(zr); + send_to_char(ch, + "Loadout saved for mob [%d]. Equipped: %d, Inventory lines: %d%s\r\n", + GET_MOB_VNUM(vict), equips_added, inv_entries, include_inv ? "" : " (use -all to include inventory)"); + mudlog(CMP, MAX(LVL_GOD, GET_INVIS_LEV(ch)), TRUE, + "msave: %s saved loadout for mob %d (eq=%d, inv=%d) in zone %d", + GET_NAME(ch), GET_MOB_VNUM(vict), equips_added, inv_entries, + zone_table[zr].number); + } + } + + inv_free_all(&inv); +} \ No newline at end of file diff --git a/src/db.c b/src/db.c index d7b2bf1..6360f66 100644 --- a/src/db.c +++ b/src/db.c @@ -1862,14 +1862,53 @@ void parse_mobile(FILE *mob_f, int nr) exit(1); } - /* DG triggers -- script info follows mob S/E section */ letter = fread_letter(mob_f); + while (letter == 'L') { + int wpos = -1, vnum = -1, qty = 1; + /* read rest of the line AFTER the leading 'L' */ + if (!get_line(mob_f, line)) { + log("SYSERR: Unexpected EOF while reading 'L' line in mob #%d.", nr); + break; + } + /* parse " [qty]" from the line buffer */ + int nread = sscanf(line, "%d %d %d", &wpos, &vnum, &qty); + if (nread < 2) { + log("SYSERR: Bad 'L' line in mob #%d: '%s' (need [qty]).", nr, line); + } else { + if (qty < 1) qty = 1; + loadout_add_entry(&mob_proto[i].proto_loadout, vnum, (sh_int)wpos, qty); + } + /* look ahead to see if there is another 'L' */ + letter = fread_letter(mob_f); + } ungetc(letter, mob_f); - while (letter=='T') { + + /* ---- DG triggers: script info follows mob S/E section ---- */ + letter = fread_letter(mob_f); + while (letter == 'T') { dg_read_trigger(mob_f, &mob_proto[i], MOB_TRIGGER); letter = fread_letter(mob_f); - ungetc(letter, mob_f); } + ungetc(letter, mob_f); + + /* ---- And allow loadout lines AFTER triggers, too ---- */ + letter = fread_letter(mob_f); + while (letter == 'L') { + int wpos = -1, vnum = -1, qty = 1; + if (!get_line(mob_f, line)) { + log("SYSERR: Unexpected EOF while reading post-trigger 'L' line in mob #%d.", nr); + break; + } + int nread = sscanf(line, "%d %d %d", &wpos, &vnum, &qty); + if (nread < 2) { + log("SYSERR: Bad post-trigger 'L' line in mob #%d: '%s' (need [qty]).", nr, line); + } else { + if (qty < 1) qty = 1; + loadout_add_entry(&mob_proto[i].proto_loadout, vnum, (sh_int)wpos, qty); + } + letter = fread_letter(mob_f); + } + ungetc(letter, mob_f); mob_proto[i].aff_abils = mob_proto[i].real_abils; @@ -2419,6 +2458,88 @@ void new_mobile_data(struct char_data *ch) ch->group = NULL; } +/* ========== Mob Loadout Auto-Equip ========== */ +static int find_alt_slot_same_family(struct char_data *ch, int intended_pos); + +/* Equip items the prototype says to wear, in those exact slots when possible. */ +void equip_mob_from_loadout(struct char_data *mob) +{ + if (!mob || !IS_NPC(mob)) return; + + mob_rnum rnum = GET_MOB_RNUM(mob); + if (rnum < 0) return; + + const struct mob_loadout *e = mob_proto[rnum].proto_loadout; + if (!e) return; + + for (; e; e = e->next) { + int qty = (e->quantity > 0) ? e->quantity : 1; + + for (int n = 0; n < qty; n++) { + struct obj_data *obj = read_object(e->vnum, VIRTUAL); + if (!obj) { + log("SYSERR: equip_mob_from_loadout: bad obj vnum %d on mob %d", + e->vnum, GET_MOB_VNUM(mob)); + continue; + } + + /* Inventory-only request */ + if (e->wear_pos < 0) { + obj_to_char(obj, mob); + continue; + } + + /* If the intended slot is free, place it there. We trust the saved slot. */ + if (e->wear_pos >= 0 && e->wear_pos < NUM_WEARS && GET_EQ(mob, e->wear_pos) == NULL) { + +#ifdef STRICT_WEAR_CHECK + /* Optional strict flag check (may be mismatched in customized codebases). */ + if (!invalid_align(mob, obj) /* example gate, add yours as needed */) { + equip_char(mob, obj, e->wear_pos); + continue; + } + /* If strict check fails, try alt or inventory below. */ +#else + equip_char(mob, obj, e->wear_pos); + continue; +#endif + } + + /* Try the mirrored slot for finger/neck/wrist pairs if intended is occupied. */ + { + int alt = find_alt_slot_same_family(mob, e->wear_pos); + if (alt >= 0 && GET_EQ(mob, alt) == NULL) { +#ifdef STRICT_WEAR_CHECK + if (!invalid_align(mob, obj)) { + equip_char(mob, obj, alt); + continue; + } +#else + equip_char(mob, obj, alt); + continue; +#endif + } + } + + /* Couldn’t place it — keep in inventory. */ + obj_to_char(obj, mob); + } + } +} + +/* Minimal “same family” alternates for symmetrical pairs. Extend if you have more. */ +static int find_alt_slot_same_family(struct char_data *ch, int intended_pos) +{ + switch (intended_pos) { + case WEAR_FINGER_R: return WEAR_FINGER_L; + case WEAR_FINGER_L: return WEAR_FINGER_R; + case WEAR_NECK_1: return WEAR_NECK_2; + case WEAR_NECK_2: return WEAR_NECK_1; + case WEAR_WRIST_R: return WEAR_WRIST_L; + case WEAR_WRIST_L: return WEAR_WRIST_R; + default: return -1; + } +} /* create a new mobile from a prototype */ struct char_data *read_mobile(mob_vnum nr, int type) /* and mob_rnum */ @@ -2438,6 +2559,7 @@ struct char_data *read_mobile(mob_vnum nr, int type) /* and mob_rnum */ clear_char(mob); *mob = mob_proto[i]; + mob->proto_loadout = NULL; /* Instances should not directly point at prototype’s loadout list */ mob->next = character_list; character_list = mob; @@ -2461,6 +2583,9 @@ struct char_data *read_mobile(mob_vnum nr, int type) /* and mob_rnum */ mob->script_id = 0; // this is set later by char_script_id + /* Equip/load items from prototype loadout before scripts fire */ + equip_mob_from_loadout(mob); + copy_proto_script(&mob_proto[i], mob, MOB_TRIGGER); assign_triggers(mob, MOB_TRIGGER); diff --git a/src/genmob.c b/src/genmob.c index 032f914..73d410f 100644 --- a/src/genmob.c +++ b/src/genmob.c @@ -380,9 +380,9 @@ int write_mobile_record(mob_vnum mvnum, struct char_data *mob, FILE *fd) ddesc, STRING_TERMINATOR ); - if(n < MAX_STRING_LENGTH) { + if (n < MAX_STRING_LENGTH) { fprintf(fd, "%s", convert_from_tabs(buf)); - + fprintf(fd, "%d %d %d %d %d %d %d %d %d E\n" "%d %d %d %dd%d+%d %dd%d+%d\n", MOB_FLAGS(mob)[0], MOB_FLAGS(mob)[1], @@ -393,29 +393,38 @@ int write_mobile_record(mob_vnum mvnum, struct char_data *mob, FILE *fd) GET_LEVEL(mob), 20 - GET_HITROLL(mob), GET_AC(mob) / 10, GET_HIT(mob), GET_MANA(mob), GET_MOVE(mob), GET_NDD(mob), GET_SDD(mob), GET_DAMROLL(mob)); - - fprintf(fd, "%d %d\n" + + fprintf(fd, "%d %d\n" "%d %d %d\n", GET_GOLD(mob), GET_EXP(mob), GET_POS(mob), GET_DEFAULT_POS(mob), GET_SEX(mob) ); - + + /* Write any E-specs */ if (write_mobile_espec(mvnum, mob, fd) < 0) log("SYSERR: GenOLC: Error writing E-specs for mobile #%d.", mvnum); - + + /* --- Persist prototype loadout lines (one per entry) --- */ + for (struct mob_loadout *e = mob->proto_loadout; e; e = e->next) { + fprintf(fd, "L %d %d %d\n", + (int)e->wear_pos, + (int)e->vnum, + MAX(1, e->quantity)); + } + + /* DG scripts after loadout lines */ script_save_to_disk(fd, mob, MOB_TRIGGER); - - - #if CONFIG_GENOLC_MOBPROG + +#if CONFIG_GENOLC_MOBPROG if (write_mobile_mobprog(mvnum, mob, fd) < 0) log("SYSERR: GenOLC: Error writing MobProgs for mobile #%d.", mvnum); - #endif +#endif } else { - mudlog(BRF,LVL_BUILDER,TRUE, + mudlog(BRF, LVL_BUILDER, TRUE, "SYSERR: Could not save mobile #%d due to size (%d > maximum of %d)", mvnum, n, MAX_STRING_LENGTH); } - + return TRUE; } diff --git a/src/interpreter.c b/src/interpreter.c index 40322d0..b3f57e4 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -200,6 +200,7 @@ cpp_extern const struct command_info cmd_info[] = { { "medit" , "med" , POS_DEAD , do_oasis_medit, LVL_BUILDER, 0 }, { "mlist" , "mlist" , POS_DEAD , do_oasis_list, LVL_BUILDER, SCMD_OASIS_MLIST }, { "mcopy" , "mcopy" , POS_DEAD , do_oasis_copy, LVL_GOD, CON_MEDIT }, + { "msave" , "msav" , POS_DEAD , do_msave, LVL_BUILDER, 0 }, { "msgedit" , "msgedit" , POS_DEAD , do_msgedit, LVL_GOD, 0 }, { "mute" , "mute" , POS_DEAD , do_wizutil , LVL_GOD, SCMD_MUTE }, diff --git a/src/structs.h b/src/structs.h index 9c276fe..ce10637 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1025,6 +1025,14 @@ struct follow_type struct follow_type *next; /**< Next character following. */ }; +/* Handles items that NPC's are loaded with ahead of time */ +struct mob_loadout { + obj_vnum vnum; /* item to clone */ + sh_int wear_pos; /* WEAR_* slot (or -1 for inventory) */ + int quantity; /* default 1; >1 for stackables or multiple clones */ + struct mob_loadout *next; +}; + /** Master structure for PCs and NPCs. */ struct char_data { @@ -1041,6 +1049,7 @@ struct char_data struct char_special_data char_specials; /**< PC/NPC specials */ struct player_special_data *player_specials; /**< PC specials */ struct mob_special_data mob_specials; /**< NPC specials */ + struct mob_loadout *proto_loadout; /* NPC objects equipped before loading NULL if none */ struct affected_type *affected; /**< affected by what spells */ struct obj_data *equipment[NUM_WEARS]; /**< Equipment array */ @@ -1312,6 +1321,16 @@ struct recent_player #define GET_STEALTH_CHECK(ch) ((ch)->char_specials.stealth_check) #define SET_STEALTH_CHECK(ch,v) ((ch)->char_specials.stealth_check = (v)) +/* NPC loadout macros */ +#define MOB_PROTO(ch) (&mob_proto[GET_MOB_RNUM(ch)]) /* Resolve proto from instance */ +#define MOB_LOADOUT_PROTO(m) ((m)->proto_loadout) /* Accessors, only prototypes should have a non-NULL list */ +#define MOB_HAS_LOADOUT(ch) (IS_NPC(ch) && (MOB_LOADOUT_PROTO(MOB_PROTO(ch)) != NULL)) /* */ + +/* NPC loadout helpers */ +void loadout_free_list(struct mob_loadout **head); +void loadout_add_entry(struct mob_loadout **head, obj_vnum vnum, sh_int wear_pos, int qty); +struct mob_loadout *loadout_deep_copy(const struct mob_loadout *src); + /* Config structs */ /** The game configuration structure used for configurating the game play diff --git a/src/utils.c b/src/utils.c index f4b7008..44c2530 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1754,3 +1754,38 @@ int GET_SITUATIONAL_AC(struct char_data *ch) int compute_armor_class_asc(struct char_data *ch) { return compute_ascending_ac(ch); } + +/* NPC loadout helpers */ +void loadout_free_list(struct mob_loadout **head) { + struct mob_loadout *n, *p = *head; + while (p) { n = p->next; free(p); p = n; } + *head = NULL; +} + +void loadout_add_entry(struct mob_loadout **head, obj_vnum vnum, sh_int wear_pos, int qty) { + struct mob_loadout *e = NULL; + if (qty < 1) qty = 1; + CREATE(e, struct mob_loadout, 1); + e->vnum = vnum; + e->wear_pos = wear_pos; + e->quantity = qty; + e->next = NULL; + /* push-front for O(1); order doesn’t matter yet */ + e->next = *head; + *head = e; +} + +struct mob_loadout *loadout_deep_copy(const struct mob_loadout *src) { + struct mob_loadout *head = NULL, *tail = NULL; + for (const struct mob_loadout *p = src; p; p = p->next) { + struct mob_loadout *n; + CREATE(n, struct mob_loadout, 1); + n->vnum = p->vnum; + n->wear_pos = p->wear_pos; + n->quantity = p->quantity; + n->next = NULL; + if (!head) head = tail = n; + else { tail->next = n; tail = n; } + } + return head; +} \ No newline at end of file