From 6add91e17080e06cae938a31c53c94e59c7f0bfb Mon Sep 17 00:00:00 2001 From: Bardur Arantsson Date: Tue, 19 Jun 2012 18:32:22 +0200 Subject: Lua: Move automatizer to C --- src/squeltch.c | 3550 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 3284 insertions(+), 266 deletions(-) (limited to 'src/squeltch.c') diff --git a/src/squeltch.c b/src/squeltch.c index 603eaa0e..d47f8a11 100644 --- a/src/squeltch.c +++ b/src/squeltch.c @@ -13,224 +13,2764 @@ #include "angband.h" #include "lua/lua.h" #include "tolua.h" +#include -extern lua_State *L; +#define RULES_MAX 4096 +#define STACK_MAX 1024 + +typedef enum { BAD, VERY_BAD, AVERAGE, + GOOD, VERY_GOOD, SPECIAL, + TERRIBLE, NONE, CHEST_EMPTY, + CHEST_DISARMED } status_type; + +struct status_map_type { + status_type status; + cptr status_s; +}; + +status_type object_status(object_type *o_ptr) +{ + if (!object_known_p(o_ptr)) + { + switch (o_ptr->sense) + { + case SENSE_CURSED: return BAD; + case SENSE_WORTHLESS: return VERY_BAD; + case SENSE_AVERAGE: return AVERAGE; + case SENSE_GOOD_LIGHT: return GOOD; + case SENSE_GOOD_HEAVY: return GOOD; + case SENSE_EXCELLENT: return VERY_GOOD; + case SENSE_SPECIAL: return SPECIAL; + case SENSE_TERRIBLE: return TERRIBLE; + default: return NONE; + } + } + else + { + s16b slot = wield_slot_ideal(o_ptr, TRUE); + + if (artifact_p(o_ptr)) + { + if (!(o_ptr->ident & IDENT_CURSED)) + { + return SPECIAL; + } + else + { + return TERRIBLE; + } + } + else if ((o_ptr->name2 > 0) || + (o_ptr->name2b > 0)) + { + if (!(o_ptr->ident & IDENT_CURSED)) + { + return VERY_GOOD; + } + else + { + return VERY_BAD; + } + } + else if ((slot == INVEN_WIELD) || + (slot == INVEN_BOW) || + (slot == INVEN_AMMO) || + (slot == INVEN_TOOL)) + { + if (o_ptr->to_h + o_ptr->to_d < 0) + { + return BAD; + } + else if (o_ptr->to_h + o_ptr->to_d > 0) + { + return GOOD; + } + else + { + return AVERAGE; + } + } + else if ((slot >= INVEN_BODY) && + (slot <= INVEN_FEET)) + { + if (o_ptr->to_a < 0) + { + return BAD; + } + else if (o_ptr->to_a > 0) + { + return GOOD; + } + else + { + return AVERAGE; + } + } + else if (slot == INVEN_RING) + { + if ((o_ptr->to_d + o_ptr->to_h < 0) || + (o_ptr->to_a < 0) || + (o_ptr->pval < 0)) + { + return BAD; + } + else + { + return AVERAGE; + } + } + else if (slot == INVEN_NECK) + { + if (o_ptr->pval < 0) + { + return BAD; + } + else + { + return AVERAGE; + } + } + else if (o_ptr->tval == TV_CHEST) + { + if (o_ptr->pval == 0) + { + return CHEST_EMPTY; + } + else if (o_ptr->pval < 0) + { + return CHEST_DISARMED; + } + else + { + return AVERAGE; + } + } + else + { + return AVERAGE; + } + } +} + +#define STATUS_MAP_SIZE 10 +struct status_map_type status_map[STATUS_MAP_SIZE] = { + { BAD, "bad" }, + { VERY_BAD, "very bad" }, + { AVERAGE, "average" }, + { GOOD, "good" }, + { VERY_GOOD, "very good" }, + { SPECIAL, "special" }, + { TERRIBLE, "terrible" }, + { NONE, "none" }, + { CHEST_EMPTY, "(empty chest)" }, + { CHEST_DISARMED, "(disarmed chest)" }, +}; + +static cptr status_to_string(status_type status) +{ + int i; + + for (i = 0; i < STATUS_MAP_SIZE; i++) + { + if (status_map[i].status == status) + { + return status_map[i].status_s; + } + } + + assert(FALSE); + return NULL; +} + +static bool_ status_from_string(cptr s, status_type *status) +{ + int i; + + for (i = 0; i < STATUS_MAP_SIZE; i++) + { + if (streq(status_map[i].status_s, s)) + { + *status = status_map[i].status; + return TRUE; + } + } + + return FALSE; +} + +/* Type of automatizer actions */ +typedef enum { AUTO_DESTROY, + AUTO_PICKUP, + AUTO_INSCRIBE } action_type; + +/* Convert action to/from string */ +struct action_map_type { + action_type action; + cptr action_s; +}; + +#define ACTION_MAP_SIZE 3 +struct action_map_type action_map[ACTION_MAP_SIZE] = { + { AUTO_DESTROY, "destroy" }, + { AUTO_PICKUP, "pickup" }, + { AUTO_INSCRIBE, "inscribe" } +}; + +static cptr action_to_string(action_type action) +{ + int i = 0; + + for (i = 0; i < ACTION_MAP_SIZE; i++) + { + if (action == action_map[i].action) + { + return action_map[i].action_s; + } + } + + assert(FALSE); + return NULL; +} + +static bool_ action_from_string(cptr s, action_type *action) +{ + int i = 0; + + for (i = 0; i < ACTION_MAP_SIZE; i++) + { + if (streq(action_map[i].action_s, s)) + { + *action = action_map[i].action; + return TRUE; + } + } + + return FALSE; +} + +/* Identification state */ +typedef enum { IDENTIFIED, NOT_IDENTIFIED } identification_state; + +#define S_IDENTIFIED "identified" +#define S_NOT_IDENTIFIED "not identified" + +cptr identification_state_to_string(identification_state i) +{ + switch (i) + { + case IDENTIFIED: return S_IDENTIFIED; + case NOT_IDENTIFIED: return S_NOT_IDENTIFIED; + } + + assert(FALSE); + return NULL; +} + +bool_ identification_state_from_string(cptr s, identification_state *state) +{ + if (streq(s, S_IDENTIFIED)) + { + *state = IDENTIFIED; + return TRUE; + } + else if (streq(s, S_NOT_IDENTIFIED)) + { + *state = NOT_IDENTIFIED; + return TRUE; + } + else + { + return FALSE; + } +} + +/* Match type */ +typedef enum { M_AND , M_OR , M_NOT , M_NAME , M_CONTAIN , + M_INSCRIBED, M_DISCOUNT, M_SYMBOL , M_STATE , M_STATUS , + M_TVAL , M_SVAL , M_RACE , M_SUBRACE , M_CLASS , + M_LEVEL , M_SKILL , M_ABILITY, M_INVENTORY, M_EQUIPMENT } + match_type; + +struct match_type_map { + match_type i; + cptr s; +}; + +#define MATCH_TYPE_MAP_SIZE 20 +struct match_type_map match_type_map[MATCH_TYPE_MAP_SIZE] = { + { M_AND, "and" }, + { M_OR, "or" }, + { M_NOT, "not" }, + { M_NAME, "name" }, + { M_CONTAIN, "contain" }, + { M_INSCRIBED, "inscribed" }, + { M_DISCOUNT, "discount" }, + { M_SYMBOL, "symbol" }, + { M_STATE, "state" }, + { M_STATUS, "status" }, + { M_TVAL, "tval" }, + { M_SVAL, "sval" }, + { M_RACE, "race" }, + { M_SUBRACE, "subrace" }, + { M_CLASS, "class" }, + { M_LEVEL, "level" }, + { M_SKILL, "skill" }, + { M_ABILITY, "ability" }, + { M_INVENTORY, "inventory" }, + { M_EQUIPMENT, "equipment" }, +}; + +cptr match_type_to_string(match_type m) +{ + int i; + + for (i = 0; i < MATCH_TYPE_MAP_SIZE; i++) + { + if (match_type_map[i].i == m) + { + return match_type_map[i].s; + } + } + + assert(FALSE); + return NULL; +} + +bool_ match_type_from_string(cptr s, match_type *match) +{ + int i; + + for (i = 0; i < MATCH_TYPE_MAP_SIZE; i++) + { + if (streq(match_type_map[i].s, s)) + { + *match = match_type_map[i].i; + return TRUE; + } + } + + return FALSE; +} + +/* Forward declarations */ +typedef struct condition_type condition_type; +struct condition_type; + +/* List of conditions */ +typedef struct condition_list condition_list; +struct condition_list { + condition_type *condition; + condition_list *next; +}; + +int compare_condition_list(condition_list *a, condition_list *b) +{ + assert(FALSE); +} + +SGLIB_DEFINE_LIST_PROTOTYPES(condition_list, compare_condition_list, next); +SGLIB_DEFINE_LIST_FUNCTIONS(condition_list, compare_condition_list, next); + +/* Condition instance */ +struct condition_type +{ + /* What do we want to match? */ + match_type match; + /* Sub-conditions for logical connectives; if applicable */ + struct { + condition_list *c; + } conditions; + /* Sub-condition for cases where there is only a single subcondition */ + condition_type *subcondition; + /* Tval to match if applicable. */ + byte tval; + /* Sval range if applicable. */ + struct { + byte min; + byte max; + } sval_range; + /* Discount range. */ + struct { + int min; + int max; + } discount; + /* Level range */ + struct { + int min; + int max; + } level_range; + /* Skill range */ + struct { + s16b min; + s16b max; + s16b skill_idx; + } skill_range; + /* Identification state to match if applicable */ + identification_state identification_state; + /* Status to match if applicable */ + status_type status; + /* Name to match */ + char *name; + /* Symbol to match if applicable */ + char symbol; + /* Inscription to find */ + char *inscription; + /* Subrace to match if applicable */ + char *subrace; + /* Race to match if applicable */ + char *race; + /* Class to match if applicable */ + char *klass; + /* Ability to match if applicable */ + s16b ability; + /* Comment */ + char *comment; +}; + +static condition_type *condition_new(match_type match) +{ + condition_type *cp = malloc(sizeof(condition_type)); + memset(cp, 0, sizeof(condition_type)); + cp->match = match; + return cp; +} + +static condition_type *condition_new_tval(byte tval) +{ + condition_type *cp = condition_new(M_TVAL); + cp->tval = tval; + return cp; +} + +static condition_type *condition_new_sval(byte min, byte max) +{ + condition_type *cp = condition_new(M_SVAL); + cp->sval_range.min = min; + cp->sval_range.max = max; + return cp; +} + +static condition_type *condition_new_and() +{ + condition_type *cp = condition_new(M_AND); + return cp; +} + +static condition_type *condition_new_or() +{ + condition_type *cp = condition_new(M_OR); + return cp; +} + +static condition_type *condition_new_not() +{ + condition_type *cp = condition_new(M_NOT); + return cp; +} + +static condition_type *condition_new_name(cptr name) +{ + condition_type *cp = condition_new(M_NAME); + cp->name = strdup(name); + return cp; +} + +static condition_type *condition_new_contain(cptr name) +{ + condition_type *cp = condition_new(M_CONTAIN); + cp->name = strdup(name); + return cp; +} + +static condition_type *condition_new_inscribed(cptr name) +{ + condition_type *cp = condition_new(M_INSCRIBED); + cp->inscription = strdup(name); + return cp; +} + +static condition_type *condition_new_status(status_type status) +{ + condition_type *cp = condition_new(M_STATUS); + cp->status = status; + return cp; +} + +static condition_type *condition_new_state(identification_state state) +{ + condition_type *cp = condition_new(M_STATE); + cp->identification_state = state; + return cp; +} + +static condition_type *condition_new_discount(int min, int max) +{ + condition_type *cp = condition_new(M_DISCOUNT); + cp->discount.min = min; + cp->discount.max = max; + return cp; +} + +static condition_type *condition_new_symbol(char c) +{ + condition_type *cp = condition_new(M_SYMBOL); + cp->symbol = c; + return cp; +} + +static condition_type *condition_new_race(cptr race) +{ + condition_type *cp = condition_new(M_RACE); + cp->race = strdup(race); + return cp; +} + +static condition_type *condition_new_subrace(cptr subrace) +{ + condition_type *cp = condition_new(M_SUBRACE); + cp->subrace = strdup(subrace); + return cp; +} + +static condition_type *condition_new_class(cptr klass) +{ + condition_type *cp = condition_new(M_CLASS); + cp->klass = strdup(klass); + return cp; +} + +static condition_type *condition_new_level(int min, int max) +{ + condition_type *cp = condition_new(M_LEVEL); + cp->level_range.min = min; + cp->level_range.max = max; + return cp; +} + +static condition_type *condition_new_skill(s16b min, s16b max, s16b skill_idx) +{ + condition_type *cp = condition_new(M_SKILL); + cp->skill_range.min = min; + cp->skill_range.max = max; + cp->skill_range.skill_idx = skill_idx; + return cp; +} + +static condition_type *condition_new_ability(s16b ability_idx) +{ + condition_type *cp = condition_new(M_ABILITY); + cp->ability = ability_idx; + return cp; +} + +static condition_type *condition_new_inventory() +{ + condition_type *cp = condition_new(M_INVENTORY); + return cp; +} + +static condition_type *condition_new_equipment() +{ + condition_type *cp = condition_new(M_EQUIPMENT); + return cp; +} + +static void condition_and_add(condition_type *and_c, condition_type *c) +{ + assert(and_c != NULL); + assert(c != NULL); + assert((and_c->match == M_AND) || (and_c->match == M_OR)); + + condition_list *cl = malloc(sizeof(condition_list)); + cl->condition = c; + cl->next = NULL; + + sglib_condition_list_add(&and_c->conditions.c, cl); +} + +static void condition_or_add(condition_type *or_c, condition_type *c) +{ + condition_and_add(or_c, c); +} + +static void condition_destroy(condition_type **cp) +{ + condition_type *c = NULL; + assert(cp != NULL); + assert(*cp != NULL); + + c = *cp; + + /* Free sub-conditions if any */ + { + condition_list *current = NULL; + condition_list *next = NULL; + + for (current = c->conditions.c; + current != NULL; + current = next) + { + condition_destroy(¤t->condition); + next = current->next; + free(current); + } + } + + /* Free sub-condition if any */ + if (c->subcondition) + { + condition_destroy(&c->subcondition); + } + + /* Free name if any */ + if (c->name) + { + free(c->name); + c->name = NULL; + } + + /* Free inscription if any */ + if (c->inscription) + { + free(c->inscription); + c->inscription = NULL; + } + + /* Free subrace if any */ + if (c->subrace) + { + free(c->subrace); + c->subrace = NULL; + } + + /* Free race if any */ + if (c->race) + { + free(c->race); + c->race = NULL; + } + + /* Free class if any */ + if (c->klass) + { + free(c->klass); + c->klass = NULL; + } + + /* Free comment if any */ + if (c->comment) + { + free(c->comment); + c->comment = NULL; + } + + /* Free the condition itself */ + free(*cp); + *cp = NULL; +} + +static bool_ condition_eval(condition_type *c, object_type *o_ptr) +{ + bool_ is_and = (c->match == M_AND); + bool_ is_or = (c->match == M_OR); + + switch (c->match) + { + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + struct condition_list *child = NULL; + + for (child = sglib_condition_list_it_init(&it, c->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) + { + if (is_and && (!condition_eval(child->condition, o_ptr))) + { + return FALSE; + } + + if (is_or && condition_eval(child->condition, o_ptr)) + { + return TRUE; + } + } + + if (is_and) + { + return TRUE; + } + else + { + return FALSE; + } + } + + case M_NOT: + { + if (c->subcondition == NULL) + { + return TRUE; + } + + return !condition_eval(c->subcondition, o_ptr); + } + + case M_INVENTORY: + { + int i; + + if (c->subcondition == NULL) + { + return FALSE; + } + + for (i = 0; i < INVEN_WIELD; i++) + { + if (condition_eval(c->subcondition, &p_ptr->inventory[i])) + { + return TRUE; + } + } + + return FALSE; + } + + case M_EQUIPMENT: + { + int i; + + if (c->subcondition == NULL) + { + return FALSE; + } + + for (i = INVEN_WIELD; i < INVEN_TOTAL; i++) + { + if (condition_eval(c->subcondition, &p_ptr->inventory[i])) + { + return TRUE; + } + } + + return FALSE; + } + + case M_NAME: + { + char buf1[128]; + char buf2[128]; + + object_desc(buf1, o_ptr, -1, 0); + strlower(buf1); + + sprintf(buf2, "%s", c->name); + strlower(buf2); + + return streq(buf1, buf2); + } + + case M_CONTAIN: + { + char buf1[128]; + char buf2[128]; + + object_desc(buf1, o_ptr, -1, 0); + strlower(buf1); + + sprintf(buf2, "%s", c->name); + strlower(buf2); + + return (strstr(buf1, buf2) != NULL); + } + + case M_SYMBOL: + { + object_kind *k_ptr = &k_info[o_ptr->k_idx]; + + return k_ptr->d_char == c->symbol; + } + + case M_INSCRIBED: + { + char buf1[128]; + char buf2[128]; + + if (o_ptr->note == 0) + { + return FALSE; + } + + sprintf(buf1, "%s", quark_str(o_ptr->note)); + strlower(buf1); + + sprintf(buf2, "%s", c->inscription); + strlower(buf2); + + return (strstr(buf1, buf2) != NULL); + } + + case M_DISCOUNT: + { + return (object_aware_p(o_ptr) && + (o_ptr->discount >= c->discount.min) && + (o_ptr->discount <= c->discount.max)); + } + + case M_TVAL: + { + return (o_ptr->tval == c->tval); + } + + case M_SVAL: + { + return (object_aware_p(o_ptr) && + (o_ptr->sval >= c->sval_range.min) && + (o_ptr->sval <= c->sval_range.max)); + } + + case M_STATUS: + { + return c->status == object_status(o_ptr); + } + + case M_STATE: + { + switch (c->identification_state) + { + case IDENTIFIED: + return object_known_p(o_ptr); + case NOT_IDENTIFIED: + return !object_known_p(o_ptr); + default: + assert(FALSE); + } + } + + case M_RACE: + { + char buf1[128]; + char buf2[128]; + + sprintf(buf1, "%s", rp_ptr->title + rp_name); + strlower(buf1); + + sprintf(buf2, "%s", c->race); + strlower(buf2); + + return streq(buf1, buf2); + } + + case M_SUBRACE: + { + char buf1[128]; + char buf2[128]; + + sprintf(buf1, "%s", rmp_ptr->title + rmp_name); + strlower(buf1); + + sprintf(buf2, "%s", c->subrace); + strlower(buf2); + + return streq(buf1, buf2); + } + + case M_CLASS: + { + char buf1[128]; + char buf2[128]; + + sprintf(buf1, "%s", spp_ptr->title + c_name); + strlower(buf1); + + sprintf(buf2, "%s", c->race); + strlower(buf2); + + return streq(buf1, buf2); + } + + case M_LEVEL: + { + return ((p_ptr->lev >= c->level_range.min) && + (p_ptr->lev <= c->level_range.max)); + } + + case M_SKILL: + { + s16b sk = get_skill(c->skill_range.skill_idx); + return ((sk >= c->skill_range.min) && + (sk <= c->skill_range.max)); + } + + case M_ABILITY: + { + return has_ability(c->ability); + } + + } + + /* Don't match by default */ + return FALSE; +} + +static json_t *condition_to_json(condition_type *c) +{ + json_t *json = NULL; + + if (c == NULL) + { + return json_null(); + } + + json = json_object(); + json_object_set_new(json, "type", + json_string(match_type_to_string(c->match))); + + switch (c->match) + { + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + struct condition_list *child = NULL; + + json_t *conditions_json = json_array(); + + for (child = sglib_condition_list_it_init(&it, c->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) + { + json_array_append_new(conditions_json, + condition_to_json(child->condition)); + } + + json_object_set_new(json, "conditions", conditions_json); + break; + } + + case M_NOT: + case M_INVENTORY: + case M_EQUIPMENT: + { + json_object_set_new(json, "condition", + condition_to_json(c->subcondition)); + break; + } + + case M_NAME: + { + json_object_set_new(json, "name", + json_string(c->name)); + break; + } + + case M_CONTAIN: + { + json_object_set_new(json, "contain", + json_string(c->name)); + break; + } + + case M_SYMBOL: + { + json_object_set_new(json, "symbol", + json_string(format("%c", c->symbol))); + break; + } + + case M_INSCRIBED: + { + json_object_set_new(json, "inscription", + json_string(c->inscription)); + break; + } + + case M_DISCOUNT: + { + json_object_set_new(json, "min", + json_integer(c->discount.min)); + json_object_set_new(json, "max", + json_integer(c->discount.max)); + break; + } + + case M_TVAL: + { + json_object_set_new(json, "tval", + json_integer(c->tval)); + break; + } + + case M_SVAL: + { + json_object_set_new(json, "min", + json_integer(c->sval_range.min)); + json_object_set_new(json, "max", + json_integer(c->sval_range.max)); + break; + } + + case M_STATUS: + { + json_object_set_new(json, "status", + json_string(status_to_string(c->status))); + break; + } + + case M_STATE: + { + json_object_set_new(json, "state", + json_string(identification_state_to_string(c->identification_state))); + break; + } + + case M_RACE: + { + json_object_set_new(json, "race", + json_string(c->race)); + break; + } + + case M_SUBRACE: + { + json_object_set_new(json, "subrace", + json_string(c->subrace)); + break; + } + + case M_CLASS: + { + json_object_set_new(json, "class", + json_string(c->klass)); + break; + } + + case M_LEVEL: + { + json_object_set_new(json, "min", + json_integer(c->level_range.min)); + json_object_set_new(json, "max", + json_integer(c->level_range.max)); + break; + } + + case M_SKILL: + { + json_object_set_new(json, "name", + json_string(s_info[c->skill_range.skill_idx].name + s_name)); + json_object_set_new(json, "min", + json_integer(c->skill_range.min)); + json_object_set_new(json, "max", + json_integer(c->skill_range.max)); + break; + } + + case M_ABILITY: + { + json_object_set_new(json, "ability", + json_string(ab_info[c->ability].name + ab_name)); + break; + } + + } + + return json; +} /* - * The functions here use direct lua stack manipulation for calls instead of - * exec_lua(format()) because string manipulations are too slow for such - * functions + * Cursor to maintain position in condition tree */ +static condition_type *cursor_stack[STACK_MAX]; +static int cursor_count = 0; + +static void cursor_push(condition_type *condition) +{ + assert(cursor_count < STACK_MAX); + + cursor_stack[cursor_count] = condition; + cursor_count++; +} + +static condition_type *cursor_pop() +{ + condition_type *c = NULL; + + assert(cursor_count > 0); + + c = cursor_stack[cursor_count-1]; + cursor_stack[cursor_count] = NULL; + cursor_count--; + return c; +} + +static condition_type *cursor_top() +{ + assert(cursor_count > 0); + + return cursor_stack[cursor_count - 1]; +} + +static void cursor_clear() +{ + while (cursor_count > 0) + { + cursor_pop(); + } +} + +/* Rule */ +typedef struct arule_type arule_type; +struct arule_type { + /* Rule name */ + char *name; + /* Which action do we take? */ + action_type action; /* Which action to take */ + /* Which module does this rule apply to? */ + int module_idx; + /* Inscription to use for inscription rules. */ + char *inscription; + /* Condition. */ + condition_type *condition; /* Condition for rule match */ +}; + +/* Initialize a rule */ +static arule_type *rule_new(cptr name, action_type action, int module_idx, condition_type *condition, cptr inscription) +{ + arule_type *rp = malloc(sizeof(arule_type)); + rp->name = strdup(name); + rp->action = action; + rp->module_idx = module_idx; + rp->condition = condition; + rp->inscription = (inscription == NULL) ? NULL : strdup(inscription); + return rp; +} + +static void rule_set_name(arule_type *rule, cptr new_name) +{ + if (rule->name) + { + free(rule->name); + } + + rule->name = strdup(new_name); +} + +static void rule_destroy(arule_type **rp) +{ + if ((*rp)->name) + { + free((*rp)->name); + } + + if ((*rp)->inscription) + { + free((*rp)->inscription); + } + + if ((*rp)->condition) + { + condition_destroy(&(*rp)->condition); + } + + free(*rp); + *rp = NULL; +} + +/* Current list of rules. */ +static arule_type *rules[RULES_MAX]; +static int rules_count = 0; /* Number of rules currently in list */ + +static int rules_append(arule_type *rule) +{ + assert(rules_count < RULES_MAX); + + rules[rules_count] = rule; + rules_count++; + return rules_count-1; +} + +static void rules_remove(arule_type *rule) +{ + int i, j; + + for (i = 0; i < rules_count; i++) + { + if (rules[i] == rule) + { + /* Free the rule */ + rule_destroy(&rule); + /* Move rest of rest "up" */ + for (j = i+1; j < rules_count; j++) + { + rules[j-1] = rules[j]; + } + /* We're done */ + rules_count--; + return; + } + } +} + +static void rules_swap(int i, int j) +{ + arule_type *tmp_rptr = NULL; + + assert(i >= 0); + assert(i < rules_count); + + assert(j >= 0); + assert(j < rules_count); + + tmp_rptr = rules[i]; + rules[i] = rules[j]; + rules[j] = tmp_rptr; +} + +static bool_* automatizer_auto_destroy(object_type *o_ptr, int item_idx) +{ + static bool_ TRUE_VAL = TRUE; + + /* Must be identified */ + if (object_aware_p(o_ptr) == FALSE) + { + return NULL; + } + + /* Inscribed things won't be destroyed! */ + if (o_ptr->note) + { + return NULL; + } + + /* Ignore artifacts; cannot be destroyed anyway. */ + if (artifact_p(o_ptr)) + { + return NULL; + } + + /* Cannot destroy CURSE_NO_DROP objects. */ + { + u32b f1, f2, f3, f4, f5, esp; + object_flags(o_ptr, &f1, &f2, &f3, &f4, &f5, &esp); + + if ((f4 & TR4_CURSE_NO_DROP) != 0) + { + return NULL; + } + } + + /* Destroy! */ + msg_print(""); + + inc_stack_size(item_idx, -o_ptr->number); + return &TRUE_VAL; +} + +static bool_* automatizer_auto_pickup(object_type *o_ptr, int item_idx) +{ + static bool_ TRUE_VAL = TRUE; + + if (item_idx >= 0) + { + return NULL; + } + + if (!inven_carry_okay(o_ptr)) + { + return NULL; + } + + msg_print(""); + + object_pickup(-item_idx); + return &TRUE_VAL; +} + +/* Apply rules */ +static bool_ apply_rule(arule_type *rule, object_type *o_ptr, int item_idx) +{ + /* Check module */ + if (rule->module_idx == game_module_idx) + { + return FALSE; + } + + /* Check condition */ + assert (rule->condition != NULL); + if (condition_eval(rule->condition, o_ptr)) + { + switch (rule->action) + { + + case AUTO_DESTROY: + { + automatizer_auto_destroy(o_ptr, item_idx); + break; + } + + case AUTO_PICKUP: + { + automatizer_auto_pickup(o_ptr, item_idx); + break; + } + + case AUTO_INSCRIBE: + { + /* Already inscribed? */ + if (o_ptr->note != 0) + { + return FALSE; + } + + /* Inscribe */ + msg_format("", rule->inscription); + o_ptr->note = quark_add(rule->inscription); + break; + } + + } + + return TRUE; + } + + return FALSE; +} + +static bool_ apply_rules(object_type *o_ptr, int item_idx) +{ + int i; + + for (i = 0; i < rules_count; i++) + { + if (apply_rule(rules[i], o_ptr, item_idx)) + { + return TRUE; + } + } + + /* Don't keep trying */ + return FALSE; +} + +static json_t *rule_to_json(arule_type *rule) +{ + json_t *rule_json = json_object(); + + json_object_set_new(rule_json, + "name", + json_string(rule->name)); + json_object_set_new(rule_json, + "action", + json_string(action_to_string(rule->action))); + json_object_set_new(rule_json, + "module", + json_string(modules[rule->module_idx].meta.name)); + + if (rule->inscription) + { + json_object_set_new(rule_json, + "inscription", + json_string(rule->inscription)); + } + + json_object_set_new(rule_json, + "condition", + condition_to_json(rule->condition)); + + return rule_json; +} + +static json_t *rules_to_json() +{ + int i; + json_t *rules_json = json_array(); + + for (i = 0; i < rules_count; i++) + { + json_array_append_new(rules_json, rule_to_json(rules[i])); + } + + return rules_json; +} + +/* Check the floor for "crap" */ +void squeltch_grid(void) +{ + s16b this_o_idx, next_o_idx = 0; + + if (!automatizer_enabled) return; + + /* Scan the pile of objects */ + for (this_o_idx = cave[p_ptr->py][p_ptr->px].o_idx; this_o_idx; this_o_idx = next_o_idx) + { + /* Acquire object */ + object_type * o_ptr = &o_list[this_o_idx]; + + /* We've now seen one of these */ + if (!k_info[o_ptr->k_idx].flavor) + { + object_aware(o_ptr); + } + + /* Acquire next object */ + next_o_idx = o_ptr->next_o_idx; + + /* Apply rules */ + apply_rules(o_ptr, -this_o_idx); + } +} + + +/* Check the inventory for "crap" */ +void squeltch_inventory(void) +{ + int i; + int num_iter = 0; + bool_ found = TRUE; + + if (!automatizer_enabled) return; + + while (found && num_iter ++ < 100) + { + /* Sometimes an index in the inventory is skipped */ + found = FALSE; + + for (i = 0; i < INVEN_PACK; i++) + { + object_type *o_ptr = &p_ptr->inventory[i]; + + if (apply_rules(o_ptr, i)) + { + found = TRUE; + break; + } + } + } + if (num_iter >= 100) + { + cmsg_format(TERM_VIOLET, "'apply_rules' ran too often."); + } +} + +/********************** The interface **********************/ +static void get_rule_names(cptr *list) +{ + int i; + + for (i = 0; i < rules_count; i++) + { + list[i] = rules[i]->name; + } +} + +typedef struct condition_metadata condition_metadata; +struct condition_metadata { + match_type match; + cptr description[3]; + condition_type *(*create_condition)(); +}; + +#define TYPES_LIST_SIZE 21 + +static condition_type *create_condition_name() +{ + cptr s = lua_input_box("Object name to match?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_name(s); +} + +static condition_type *create_condition_contain() +{ + cptr s = lua_input_box("Word to find in object name?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_contain(s); +} + +static condition_type *create_condition_inscribed() +{ + cptr s = lua_input_box("Word to find in object inscription?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_inscribed(s); +} + +static condition_type *create_condition_discount() +{ + int min, max; + + { + cptr s = lua_input_box("Min discount?", 79); + if (sscanf(s, "%d", &min) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Max discount?", 79); + if (sscanf(s, "%d", &max) < 1) + { + return NULL; + } + } + + return condition_new_discount(min, max); +} + +static condition_type *create_condition_symbol() +{ + char c; + cptr s = lua_input_box("Symbol to match?", 1); + if (sscanf(s, "%c", &c) < 1) + { + return NULL; + } + + return condition_new_symbol(c); +} + +static condition_type *create_condition_status() +{ + status_type status; + char c; + + c = lua_msg_box("[t]errible, [v]ery bad, [b]ad, " + "[a]verage, [G]ood, [V]ery good, [S]pecial?"); + + switch (c) + { + case 't': status = TERRIBLE; break; + case 'v': status = VERY_BAD; break; + case 'b': status = BAD; break; + case 'a': status = AVERAGE; break; + case 'G': status = GOOD; break; + case 'V': status = VERY_GOOD; break; + case 'S': status = SPECIAL; break; + default: return NULL; + } + + return condition_new_status(status); +} + +static condition_type *create_condition_state() +{ + identification_state s; + char c; + + c = lua_msg_box("[i]dentified, [n]on identified?"); + + switch (c) + { + case 'i': s = IDENTIFIED; break; + case 'n': s = NOT_IDENTIFIED; break; + default: return NULL; + } + + return condition_new_state(s); +} + +static condition_type *create_condition_tval() +{ + int tval; + cptr s = lua_input_box("Tval to match?", 79); + if (sscanf(s, "%d", &tval) < 1) + { + return NULL; + } + + return condition_new_tval(tval); +} + +static condition_type *create_condition_sval() +{ + int sval_min, sval_max; + + { + cptr s = lua_input_box("Min sval?", 79); + if (sscanf(s, "%d", &sval_min) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Max sval?", 79); + if (sscanf(s, "%d", &sval_max) < 1) + { + return NULL; + } + } + + return condition_new_sval(sval_min, sval_max); +} + +static condition_type *create_condition_race() +{ + cptr s = lua_input_box("Player race to match?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_race(s); +} + +static condition_type *create_condition_subrace() +{ + cptr s = lua_input_box("Player subrace to match?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_subrace(s); +} + +static condition_type *create_condition_class() +{ + cptr s = lua_input_box("Player class to match?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_class(s); +} + +static condition_type *create_condition_level() +{ + int min, max; + + { + cptr s = lua_input_box("Min player level?", 79); + if (sscanf(s, "%d", &min) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Max player level?", 79); + if (sscanf(s, "%d", &max) < 1) + { + return NULL; + } + } + + return condition_new_level(min, max); +} + +static condition_type *create_condition_skill() +{ + int min, max; + s16b skill_idx; + + { + cptr s = lua_input_box("Min skill level?", 79); + if (sscanf(s, "%d", &min) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Max skill level?", 79); + if (sscanf(s, "%d", &max) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Skill name?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + skill_idx = find_skill_i(s); + if (skill_idx < 0) + { + return NULL; + } + } + + return condition_new_skill(min, max, skill_idx); +} + +static condition_type *create_condition_ability() +{ + s16b ai; + cptr s = lua_input_box("Ability name?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + ai = find_ability(s); + if (ai < 0) + { + return NULL; + } + + return condition_new_ability(ai); +} + +static condition_metadata types_list[TYPES_LIST_SIZE] = +{ + { M_AND, + { "Check is true if all rules within it are true", + NULL }, + condition_new_and, + }, + { M_OR, + { "Check is true if at least one rule within it is true", + NULL }, + condition_new_or, + }, + { M_NOT, + { "Invert the result of its child rule", + NULL }, + condition_new_not, + }, + { M_NAME, + { "Check is true if object name matches name", + NULL }, + create_condition_name, + }, + { M_CONTAIN, + { "Check is true if object name contains word", + NULL }, + create_condition_contain, + }, + { M_INSCRIBED, + { "Check is true if object inscription contains word", + NULL }, + create_condition_inscribed, + }, + { M_DISCOUNT, + { "Check is true if object discount is between two values", + NULL }, + create_condition_discount, + }, + { M_SYMBOL, + { "Check is true if object symbol is ok", + NULL }, + create_condition_symbol, + }, + { M_STATE, + { "Check is true if object is identified/unidentified", + NULL }, + create_condition_state, + }, + { M_STATUS, + { "Check is true if object status is ok", + NULL }, + create_condition_status, + }, + { M_TVAL, + { "Check is true if object tval(from k_info.txt) is ok", + NULL }, + create_condition_tval, + }, + { M_SVAL, + { "Check is true if object sval(from k_info.txt) is between", + "two values", + NULL }, + create_condition_sval, + }, + { M_RACE, + { "Check is true if player race is ok", + NULL }, + create_condition_race, + }, + { M_SUBRACE, + { "Check is true if player subrace is ok", + NULL }, + create_condition_subrace, + }, + { M_CLASS, + { "Check is true if player class is ok", + NULL }, + create_condition_class, + }, + { M_LEVEL, + { "Check is true if player level is between 2 values", + NULL }, + create_condition_level, + }, + { M_SKILL, + { "Check is true if player skill level is between 2 values", + NULL }, + create_condition_skill, + }, + { M_ABILITY, + { "Check is true if player has the ability", + NULL }, + create_condition_ability, + }, + { M_INVENTORY, + { "Check is true if something in player's inventory matches", + "the contained rule", + NULL }, + condition_new_inventory, + }, + { M_EQUIPMENT, + { "Check is true if something in player's equipment matches", + "the contained rule", + NULL }, + condition_new_equipment, + }, +}; + +static void display_desc(condition_metadata *condition_metadata) +{ + int i; + + assert(condition_metadata != NULL); + + for (i = 0; condition_metadata->description[i] != NULL; i++) + { + c_prt(TERM_WHITE, condition_metadata->description[i], i + 1, 17); + } +} + +/* Create a new rule */ +static condition_metadata *automatizer_select_condition_type() +{ + int wid, hgt, max = TYPES_LIST_SIZE, begin = 0, sel = 0, i; + char c; + cptr types_names[TYPES_LIST_SIZE]; + + /* Create list of names for display */ + for (i = 0; i < TYPES_LIST_SIZE; i++) + { + types_names[i] = match_type_to_string(types_list[i].match); + } + + while (1) + { + Term_clear(); + Term_get_size(&wid, &hgt); + + display_list(0, 0, hgt - 1, 15, "Rule types", types_names, max, begin, sel, TERM_L_GREEN); + + display_desc(&types_list[sel]); + + c = inkey(); + + if (c == ESCAPE) break; + else if (c == '8') + { + sel--; + if (sel < 0) + { + sel = max - 1; + begin = max - hgt; + if (begin < 0) begin = 0; + } + if (sel < begin) begin = sel; + } + else if (c == '2') + { + sel++; + if (sel >= max) + { + sel = 0; + begin = 0; + } + if (sel >= begin + hgt - 1) begin++; + } + else if (c == '\r') + { + return &types_list[sel]; + } + } + return NULL; +} + +static void adjust_begin(int *begin, int *sel, int max, int hgt) +{ + if (*sel < 0) + { + *sel = max - 1; + *begin = *sel - hgt + 3; + if (*begin < 0) *begin = 0; + } + if (*sel < *begin) *begin = *sel; + + if (*sel >= max) + { + *sel = 0; + *begin = 0; + } + if (*sel >= *begin + hgt - 2) (*begin)++; +} + +static int create_new_rule() +{ + action_type action; + char name[20] = { '\0' }; + char *inscription = NULL; + int wid = 0, hgt = 0; + char typ; + arule_type *rule = NULL; + + Term_get_size(&wid, &hgt); + + sprintf(name, "%s", "No name"); + if (!input_box("Name?", hgt / 2, wid / 2, name, sizeof(name)+1)) + { + return -1; + } + + typ = lua_msg_box("[D]estroy, [P]ickup, [I]nscribe?"); + + switch (typ) + { + case 'd': + case 'D': + action = AUTO_DESTROY; + break; + + case 'p': + case 'P': + action = AUTO_PICKUP; + break; + + case 'i': + case 'I': + { + char *i = NULL; + + action = AUTO_INSCRIBE; + + i = lua_input_box("Inscription?", 79); + if ((i == NULL) || (strlen(i) == 0)) + { + return -1; + } + + inscription = i; + + break; + } + + default: + return -1; + } + + /* Make rule */ + rule = rule_new(name, action, game_module_idx, NULL, inscription); + + /* Append to list of rules */ + return rules_append(rule); +} + +static void add_child(condition_type *current) +{ + condition_metadata *metadata = NULL; + + switch (current->match) + { + case M_NOT: + case M_EQUIPMENT: + case M_INVENTORY: + { + if (current->subcondition != NULL) + { + return; + } + + metadata = automatizer_select_condition_type(); + if (metadata == NULL) + { + return; + } + + current->subcondition = metadata->create_condition(); + break; + } + + case M_AND: + metadata = automatizer_select_condition_type(); + if (metadata == NULL) + { + return; + } + condition_and_add(current, metadata->create_condition()); + break; + + case M_OR: + metadata = automatizer_select_condition_type(); + if (metadata == NULL) + { + return; + } + condition_or_add(current, metadata->create_condition()); + break; + + default: + /* No other types of conditions have children */ + break; + } +} + +static int tree_indent = 0; +static int tree_write_out_y = 0; +static int tree_write_out_x = 0; +static int tree_write_out_h = 0; +static int tree_write_out_w = 0; +static int tree_write_y = 0; +static int tree_write_x = 0; +static int tree_write_off_x = 0; +static int tree_write_off_y = 0; + +static void tree_write(byte color, cptr line) +{ + cptr p = line; + + for (p = line; *p != '\0'; p++) + { + char c = *p; + int x = tree_write_x - tree_write_off_x + 3*tree_indent; + int y = tree_write_y - tree_write_off_y; + + if (c != '\n') + { + if ((y >= 0) && + (y < tree_write_out_h) && + (x >= 0) && + (x < tree_write_out_w)) + { + Term_putch(x + tree_write_out_x, + y + tree_write_out_y, + color, + c); + } + + tree_write_x += 1; + } + else + { + tree_write_x = 0; + tree_write_y += 1; + } + } +} + +static void display_condition(condition_type *condition) +{ + byte bcol = TERM_L_GREEN; + byte ecol = TERM_GREEN; + int i; + + assert(condition != NULL); + + /* If this condition is present in the cursor stack, + then we use the "active" colors. */ + for (i = 0; i < cursor_count; i++) + { + if (cursor_stack[i] == condition) + { + bcol = TERM_VIOLET; + ecol = TERM_VIOLET; + break; + } + } + + tree_indent++; + + switch (condition->match) + { + case M_INVENTORY: + case M_EQUIPMENT: + { + cptr where_s = (condition->match == M_INVENTORY) + ? "inventory" + : "equipment"; + + tree_write(ecol, "Something in your "); + tree_write(bcol, where_s); + tree_write(ecol, " matches the following:"); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_SKILL: + { + cptr skill_name = + s_info[condition->skill_range.skill_idx].name + s_name; + + tree_write(ecol, "Your skill in "); + tree_write(bcol, skill_name); + tree_write(ecol, " is from "); + tree_write(TERM_WHITE, format("%d", (int) condition->skill_range.min)); + tree_write(ecol, " to "); + tree_write(TERM_WHITE, format("%d", (int) condition->skill_range.max)); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_ABILITY: + { + cptr ability_name = + ab_info[condition->ability].name + ab_name; + + tree_write(ecol, "You have the "); + tree_write(bcol, ability_name); + tree_write(ecol, " ability"); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_LEVEL: + { + tree_write(ecol, "Your "); + tree_write(bcol, "level"); + tree_write(ecol, " is from "); + + tree_write(TERM_WHITE, format("%d", condition->level_range.min)); + tree_write(ecol, " to "); + tree_write(TERM_WHITE, format("%d", condition->level_range.max)); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_SVAL: + { + tree_write(ecol, "Its "); + tree_write(bcol, "sval"); + tree_write(ecol, " is from "); + tree_write(TERM_WHITE, format("%d", condition->sval_range.min)); + tree_write(ecol, " to "); + tree_write(TERM_WHITE, format("%d", condition->sval_range.max)); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_DISCOUNT: + { + tree_write(ecol, "Its "); + tree_write(bcol, "discount"); + tree_write(ecol, " is from "); + tree_write(TERM_WHITE, format("%d", condition->discount.min)); + tree_write(ecol, " to "); + tree_write(TERM_WHITE, format("%d", condition->discount.max)); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_AND: + { + struct sglib_condition_list_iterator it; + struct condition_list *child = NULL; + + tree_write(ecol, "All of the following are true:"); + tree_write(TERM_WHITE, "\n"); + + for (child = sglib_condition_list_it_init(&it, condition->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) + { + display_condition(child->condition); + } + + break; + } + + case M_OR: + { + struct sglib_condition_list_iterator it; + struct condition_list *child = NULL; + + tree_write(ecol, "At least one of the following are true:"); + tree_write(TERM_WHITE, "\n"); + + for (child = sglib_condition_list_it_init(&it, condition->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) + { + display_condition(child->condition); + } + + break; + } -/* Check the floor for "crap" */ -void squeltch_grid(void) -{ - int oldtop; - s16b this_o_idx, next_o_idx = 0; + case M_NOT: + { + tree_write(ecol, "Negate the following:"); + tree_write(TERM_WHITE, "\n"); + display_condition(condition->subcondition); + break; + } - if (!automatizer_enabled) return; + case M_NAME: + { + tree_write(ecol, "Its "); + tree_write(bcol, "name"); + tree_write(ecol, " is \""); + tree_write(TERM_WHITE, condition->name); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - oldtop = lua_gettop(L); + case M_CONTAIN: + { + tree_write(ecol, "Its "); + tree_write(bcol, "name"); + tree_write(ecol, " contains \""); + tree_write(TERM_WHITE, condition->name); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Scan the pile of objects */ - for (this_o_idx = cave[p_ptr->py][p_ptr->px].o_idx; this_o_idx; this_o_idx = next_o_idx) + case M_INSCRIBED: { - /* Acquire object */ - object_type * o_ptr = &o_list[this_o_idx]; + tree_write(ecol, "It is "); + tree_write(bcol, "inscribed"); + tree_write(ecol, " with "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, condition->inscription); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* We've now seen one of these */ - if (!k_info[o_ptr->k_idx].flavor) - { - object_aware(o_ptr); - } + case M_SYMBOL: + { + tree_write(ecol, "Its "); + tree_write(bcol, "symbol"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, format("%c", condition->symbol)); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Acquire next object */ - next_o_idx = o_ptr->next_o_idx; + case M_STATE: + { + tree_write(ecol, "Its "); + tree_write(bcol, "state"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, identification_state_to_string(condition->identification_state)); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_STATUS: + { + tree_write(ecol, "Its "); + tree_write(bcol, "status"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, status_to_string(condition->status)); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_TVAL: + { + tree_write(ecol, "Its "); + tree_write(bcol, "tval"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, format("%d", condition->tval)); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Push the function */ - lua_settop(L, oldtop); - lua_getglobal(L, "apply_rules"); + case M_RACE: + { + tree_write(ecol, "Player "); + tree_write(bcol, "race"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, condition->race); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Push the args */ - tolua_pushusertype(L, o_ptr, tolua_tag(L, "object_type")); - tolua_pushnumber(L, -this_o_idx); + case M_SUBRACE: + { + tree_write(ecol, "Player "); + tree_write(bcol, "subrace"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, condition->subrace); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_CLASS: + { + tree_write(ecol, "Player "); + tree_write(bcol, "class"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, condition->klass); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Call the function */ - if (lua_call(L, 2, 0)) - { - cmsg_format(TERM_VIOLET, "ERROR in lua_call while calling 'apply_rules'."); - lua_settop(L, oldtop); - return; - } } - lua_settop(L, oldtop); + + tree_indent--; } +static void display_rule(arule_type *rule) +{ + cptr action_s; + int hgt, wid; -/* Check the inventory for "crap" */ -void squeltch_inventory(void) + action_s = action_to_string(rule->action); + + Term_get_size(&wid, &hgt); + + tree_write_out_y = 1; + tree_write_out_x = 16; + tree_write_out_h = hgt - 4 - 1; + tree_write_out_w = wid - 1 - 15 - 1; + tree_write_y = 0; + tree_write_x = 0; + + switch (rule->action) + { + case AUTO_DESTROY: + case AUTO_PICKUP: + { + tree_write(TERM_GREEN, "A rule named \""); + tree_write(TERM_WHITE, rule->name); + tree_write(TERM_GREEN, "\" to "); + tree_write(TERM_L_GREEN, action_s); + tree_write(TERM_GREEN, " when"); + tree_write(TERM_WHITE, "\n"); + break; + } + + case AUTO_INSCRIBE: + { + tree_write(TERM_GREEN, "A rule named \""); + tree_write(TERM_WHITE, rule->name); + tree_write(TERM_GREEN, "\" to "); + tree_write(TERM_L_GREEN, "inscribe"); + tree_write(TERM_GREEN, " an item with \""); + tree_write(TERM_WHITE, rule->inscription); + tree_write(TERM_GREEN, "\" when"); + tree_write(TERM_WHITE, "\n"); + break; + } + + } + + /* Write out the condition */ + if (rule->condition != NULL) + { + display_condition(rule->condition); + } +} + +static void adjust_current(int sel) { - int oldtop; - int i; - int num_iter = 0; - bool_ found = TRUE; + if (rules_count == 0) + { + cursor_clear(); + return; + } - if (!automatizer_enabled) return; + tree_write_off_y = 0; + tree_write_off_x = 0; - oldtop = lua_gettop(L); - while (found && num_iter ++ < 100) + /* Put the top-level condition into cursor */ + cursor_clear(); + if (rules[sel]->condition != NULL) { - /* Sometimes an index in the inventory is skipped */ - found = FALSE; + cursor_push(rules[sel]->condition); + } +} - for (i = 0; i < INVEN_PACK; i++) - { - object_type *o_ptr = &p_ptr->inventory[i]; +static void tree_scroll_up() +{ + tree_write_off_y = tree_write_off_y - 1; +} - /* Push the function */ - lua_settop(L, oldtop); - lua_getglobal(L, "apply_rules"); +static void tree_scroll_down() +{ + tree_write_off_y = tree_write_off_y + 1; +} - /* Push the args */ - tolua_pushusertype(L, o_ptr, tolua_tag(L, "object_type")); - tolua_pushnumber(L, i); +static void tree_scroll_left() +{ + tree_write_off_x = tree_write_off_x + 1; +} - /* Call the function */ - if (lua_call(L, 2, 1)) - { - cmsg_format(TERM_VIOLET, "ERROR in lua_call while calling 'apply_rules'."); - lua_settop(L, oldtop); - return; - } +static void tree_scroll_right() +{ + tree_write_off_x = tree_write_off_x - 1; +} - /* Did it return TRUE */ - if (tolua_getnumber(L, -(lua_gettop(L) - oldtop), 0)) - { - found = TRUE; - break; - } +static void automatizer_save_rules() +{ + char name[30] = { '\0' }; + char buf[1025]; + char ch; + int hgt, wid; + + Term_get_size(&wid, &hgt); + + sprintf(name, "automat.atm"); + if (!input_box("Save name?", hgt / 2, wid / 2, name, sizeof(name)+1)) + { + return; + } + + /* Build the filename */ + path_build(buf, 1024, ANGBAND_DIR_USER, name); + + /* File type is "TEXT" */ + FILE_TYPE(FILE_TYPE_TEXT); + + if (file_exist(buf)) + { + c_put_str(TERM_WHITE, "File exists, continue?[y/n]", + hgt / 2, + wid / 2 - 14); + ch = inkey(); + if ((ch != 'Y') && (ch != 'y')) + { + return; } } - if (num_iter >= 100) + + /* Write to file */ { - cmsg_format(TERM_VIOLET, "'apply_rules' ran too often."); + json_t *rules_json = rules_to_json(); + int status = json_dump_file(rules_json, buf, JSON_INDENT(2) | JSON_SORT_KEYS); + if (status == 0) + { + c_put_str(TERM_WHITE, "Saved rules in file ", + hgt / 2, + wid / 2 - 14); + } + else + { + c_put_str(TERM_WHITE, "Saving rules failed! ", + hgt / 2, + wid / 2 - 14); + } + + /* Deallocate JSON */ + json_decref(rules_json); + + /* Wait for keypress */ + inkey(); } - lua_settop(L, oldtop); } -/********************** The interface **********************/ -static cptr *get_rule_list(int *max) +static void rename_rule(arule_type *rule) { - cptr *list; - int i; + char name[16]; + int wid, hgt; - *max = exec_lua("return __rules_max"); - C_MAKE(list, *max, cptr); - - for (i = 0; i < *max; i++) - { - list[i] = string_exec_lua(format("return __rules[%d].table.args.name", i)); - } - - return list; -} - -static cptr types_list[] = -{ - "and", - "or", - "not", - "name", - "contain", - "inscribed", - "discount", - "symbol", - "state", - "status", - "tval", - "sval", - "race", - "subrace", - "class", - "level", - "skill", - "ability", - "inventory", - "equipment", - "comment", - NULL, -}; + assert(rule != NULL); -/* Create a new rule */ -static int automatizer_new_rule() + Term_get_size(&wid, &hgt); + + sprintf(name, "%s", rule->name); + if (input_box("New name?", hgt / 2, wid / 2, name, sizeof(name)-1)) + { + rule_set_name(rule, name); + } +} + +static void add_new_condition(arule_type *current_rule) { - int wid, hgt, max, begin = 0, sel = 0; - char c; + /* Top-level condition? */ + if (current_rule->condition == NULL) + { + condition_metadata *metadata = NULL; - /* Get the number of types */ - max = 0; - while (types_list[max] != NULL) - max++; + /* Sanity check for navigation stack */ + assert(cursor_count == 0); - while (1) + /* Select type of clause */ + metadata = automatizer_select_condition_type(); + if (metadata == NULL) + { + return; + } + + /* Create the condition directly; we can + always add a top-level condition so there's + no need for the sanity checking in + add_child(). */ + current_rule->condition = metadata->create_condition(); + if (current_rule->condition != NULL) + { + cursor_push(current_rule->condition); + } + } + else { - Term_clear(); - Term_get_size(&wid, &hgt); + condition_type *current_condition = cursor_top(); + add_child(current_condition); + } +} + +static void tree_go_right() +{ + condition_type *top = cursor_top(); + + /* Can only go right if the currently selected condition + has children. */ + switch (top->match) + { + case M_AND: + case M_OR: + { + /* Pick first child */ + struct sglib_condition_list_iterator it; + condition_list *i = sglib_condition_list_it_init(&it, top->conditions.c); + /* Move right if possible */ + if (i != NULL) + { + cursor_push(i->condition); + } + break; + } - display_list(0, 0, hgt - 1, 15, "Rule types", types_list, max, begin, sel, TERM_L_GREEN); + case M_NOT: + case M_INVENTORY: + case M_EQUIPMENT: + { + if (top->subcondition != NULL) + { + cursor_push(top->subcondition); + } + break; + } - exec_lua(format("auto_aux:display_desc('%s')", types_list[sel])); - c_prt(TERM_YELLOW, "Example:", 5, 17); - exec_lua(format("xml.write_out_y = 6; xml.write_out_x = 16; xml.write_out_h = %d; xml.write_out_w = %d; xml.write_y = 0; xml.write_x = 0; xml:display_xml(auto_aux.types_desc['%s'][2][1], '')", hgt - 3 - 5, wid - 1 - 15 - 1, types_list[sel])); + default: + /* Not possible to move */ + break; + } +} - c = inkey(); +static void tree_go_left() +{ + if (cursor_count > 1) + { + cursor_pop(); + } +} - if (c == ESCAPE) break; - else if (c == '8') +static void tree_go_up() +{ + if (cursor_count > 1) + { + condition_type *prev_top = cursor_pop(); + condition_type *top = cursor_top(); + + switch (top->match) { - sel--; - if (sel < 0) + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + condition_list *child = NULL; + condition_list *prev_child = NULL; + + /* We have a list of children */ + for (child = sglib_condition_list_it_init(&it, top->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) { - sel = max - 1; - begin = max - hgt; - if (begin < 0) begin = 0; + if (child->condition == prev_top) + { + /* Do we have a previous child? */ + if (prev_child == NULL) + { + /* No predecessor; don't move */ + cursor_push(prev_top); + break; + } + else + { + cursor_push(prev_child->condition); + break; /* Done */ + } + } + /* Keep track of previous child */ + prev_child = child; } - if (sel < begin) begin = sel; + + break; } - else if (c == '2') + + default: { - sel++; - if (sel >= max) + /* No other match types have children; restore + original top. */ + cursor_push(prev_top); + break; + } + + } + } +} + +static void tree_go_down() +{ + if (cursor_count > 1) + { + condition_type *prev_top = cursor_pop(); + condition_type *top = cursor_top(); + + switch (top->match) + { + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + condition_list *child = NULL; + + /* We have a list of children */ + for (child = sglib_condition_list_it_init(&it, top->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) { - sel = 0; - begin = 0; + if (child->condition == prev_top) + { + /* Move to next child (if any) */ + child = sglib_condition_list_it_next(&it); + if (child == NULL) + { + /* No successor; don't move */ + cursor_push(prev_top); + break; + } + else + { + cursor_push(child->condition); + break; /* Done */ + } + } } - if (sel >= begin + hgt - 1) begin++; + + break; } - else if (c == '\r') + + default: { - return sel; + /* No other match types have multiple children; restore + original top. */ + cursor_push(prev_top); + break; + } + } } - return -1; } -static void adjust_begin(int *begin, int *sel, int max, int hgt) +static int automatizer_del_self(int sel) { - if (*sel < 0) + /* If the cursor is at the top level then + we want to delete the rule itself */ + if (cursor_count < 1) { - *sel = max - 1; - *begin = *sel - hgt + 3; - if (*begin < 0) *begin = 0; + rules_remove(rules[sel]); + return sel - 1; /* Move selection up */ } - if (*sel < *begin) *begin = *sel; - - if (*sel >= max) + else if (cursor_count == 1) { - *sel = 0; - *begin = 0; + cursor_pop(); + condition_destroy(&rules[sel]->condition); + return sel; + } + else + { + condition_type *prev_top = cursor_pop(); + condition_type *top = cursor_top(); + + /* Jump up a level; this is a simple way to ensure a + valid cursor. We could be a little cleverer here by + trying to move inside the current level, but it's a + little complicated. */ + tree_go_left(); + + /* Now we can remove the condition from its parent */ + switch (top->match) + { + + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + condition_list *item = NULL; + + /* We have a list of children */ + for (item = sglib_condition_list_it_init(&it, top->conditions.c); + item != NULL; + item = sglib_condition_list_it_next(&it)) + { + if (item->condition == prev_top) + { + /* Found */ + break; + } + } + + /* Must have found item; otherwise internal structure + is damaged. */ + assert (item != NULL); + sglib_condition_list_delete(&top->conditions.c, item); + + /* Destroy the condition */ + condition_destroy(&prev_top); + break; + } + + case M_NOT: + case M_EQUIPMENT: + case M_INVENTORY: + { + assert(top->subcondition != NULL); + condition_destroy(&top->subcondition); + break; + } + + default: + /* If we get here, something's wrong with the + navigation structures. */ + assert(FALSE); + break; + } + + /* Keep selection */ + return sel; } - if (*sel >= *begin + hgt - 2) (*begin)++; } #define ACTIVE_LIST 0 @@ -239,12 +2779,12 @@ void do_cmd_automatizer() { int wid = 0, hgt = 0; char c; - cptr *list = NULL; int max, begin = 0, sel = 0; int active = ACTIVE_LIST; cptr keys; cptr keys2; cptr keys3; + cptr rule_names[RULES_MAX]; Term_get_size(&wid, &hgt); @@ -260,37 +2800,38 @@ void do_cmd_automatizer() screen_save(); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + adjust_current(sel); while (1) { Term_clear(); Term_get_size(&wid, &hgt); - list = get_rule_list(&max); - display_list(0, 0, hgt - 1, 15, "Rules", list, max, begin, sel, (active == ACTIVE_LIST) ? TERM_L_GREEN : TERM_GREEN); - C_FREE(list, max, cptr); + max = rules_count; + get_rule_names(rule_names); + display_list(0, 0, hgt - 1, 15, "Rules", rule_names, max, begin, sel, (active == ACTIVE_LIST) ? TERM_L_GREEN : TERM_GREEN); draw_box(0, 15, hgt - 4, wid - 1 - 15); if (active == ACTIVE_RULE) { keys = "#Bup#W/#Bdown#W/#Bleft#W/#Bright#W to navitage rule, #B9#W/#B3#W/#B7#W/#B1#W to scroll"; keys2 = "#Btab#W for switch, #Ba#Wdd clause, #Bd#Welete clause/rule"; - keys3 = "#Bx#W to toggle english/xml, #G?#W for Automatizer help"; - exec_lua("xml.write_active = not nil"); + keys3 = "#G?#W for Automatizer help"; } else { keys = "#Bup#W/#Bdown#W to scroll, #Btab#W to switch to the rule window"; keys2 = "#Bu#W/#Bd#W to move rules, #Bn#Wew rule, #Br#Wename rule, #Bs#Wave rules"; keys3 = "#Rk#W to #rdisable#W the automatizer, #G?#W for Automatizer help"; - exec_lua("xml.write_active = nil"); } display_message(17, hgt - 3, strlen(keys), TERM_WHITE, keys); display_message(17, hgt - 2, strlen(keys2), TERM_WHITE, keys2); display_message(17, hgt - 1, strlen(keys3), TERM_WHITE, keys3); - if (max) exec_lua(format("xml.write_out_y = 1; xml.write_out_x = 16; xml.write_out_h = %d; xml.write_out_w = %d; xml.write_y = 0; xml.write_x = 0; xml:display_xml(__rules[%d].table, '')", hgt - 4 - 1, wid - 1 - 15 - 1, sel)); + if (max) + { + display_rule(rules[sel]); + } c = inkey(); @@ -308,105 +2849,58 @@ void do_cmd_automatizer() if (!max) continue; sel--; adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + adjust_current(sel); } else if (c == '2') { if (!max) continue; sel++; adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + adjust_current(sel); } else if (c == 'u') { - if (!max) continue; - sel = exec_lua(format("return auto_aux:move_up(%d)", sel)); - adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + if (sel > 0) + { + rules_swap(sel-1, sel); + sel -= 1; + + adjust_begin(&begin, &sel, max, hgt); + adjust_current(sel); + } } else if (c == 'd') { if (!max) continue; - sel = exec_lua(format("return auto_aux:move_down(%d)", sel)); - adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + + if (sel < rules_count - 1) + { + rules_swap(sel, sel+1); + sel += 1; + + adjust_begin(&begin, &sel, max, hgt); + adjust_current(sel); + } } else if (c == 'n') { - char name[20] = { '\0' }; - char typ; - - sprintf(name, "No name"); - if (input_box("Name?", hgt / 2, wid / 2, name, sizeof(name)+1)) + int i = create_new_rule(); + if (i >= 0) { - cptr styp = "nothing"; - typ = msg_box("[D]estroy, [P]ickup, [I]nscribe, [N]othing rule?", hgt / 2, wid / 2); - if ((typ == 'd') || (typ == 'D')) styp = "destroy"; - else if ((typ == 'p') || (typ == 'P')) styp = "pickup"; - else if ((typ == 'i') || (typ == 'I')) styp = "inscribe"; - exec_lua(format("auto_aux:new_rule(%d, '%s', '%s', ''); auto_aux:adjust_current(%d)", sel, name, styp, sel)); + sel = i; + adjust_current(sel); active = ACTIVE_RULE; } } else if (c == 's') { - char name[30] = { '\0' }; - - sprintf(name, "automat.atm"); - if (input_box("Save name?", hgt / 2, wid / 2, name, sizeof(name)+1)) - { - char buf[1025]; - char ch; - - /* Build the filename */ - path_build(buf, 1024, ANGBAND_DIR_USER, name); - - /* File type is "TEXT" */ - FILE_TYPE(FILE_TYPE_TEXT); - - if (file_exist(buf)) - { - c_put_str(TERM_WHITE, "File exists, continue?[y/n]", hgt / 2, wid / 2 - 14); - ch = inkey(); - if ((ch != 'Y') && (ch != 'y')) - continue; - } - - /* Open the non-existing file */ - hook_file = my_fopen(buf, "w"); - - /* Invalid file */ - if (!hook_file) - { - /* Message */ - c_put_str(TERM_WHITE, "Saving rules failed! ", hgt / 2, wid / 2 - 14); - (void) inkey(); - - /* Error */ - continue; - } - - - - exec_lua("auto_aux:save_ruleset()"); - my_fclose(hook_file); - /* Overwrite query message */ - c_put_str(TERM_WHITE, "Saved rules in file ", hgt / 2, wid / 2 - 14); - (void) inkey(); - } + automatizer_save_rules(); } else if (c == 'r') { - char name[20]; - if (!max) continue; - sprintf(name, "%s", string_exec_lua(format("return __rules[%d].table.args.name", sel))); - if (input_box("New name?", hgt / 2, wid / 2, name, 15)) - { - exec_lua(format("auto_aux:rename_rule(%d, '%s')", sel, name)); - } - + rename_rule(rules[sel]); continue; } else if (c == 'k') @@ -419,10 +2913,6 @@ void do_cmd_automatizer() if (!max) continue; active = ACTIVE_RULE; } - else if (c == 'x') - { - exec_lua("xml.display_english = not xml.display_english"); - } } else if (active == ACTIVE_RULE) { @@ -434,41 +2924,39 @@ void do_cmd_automatizer() } else if (c == '8') { - exec_lua("auto_aux:go_up()"); + tree_go_up(); } else if (c == '2') { - exec_lua("auto_aux:go_down()"); + tree_go_down(); } else if (c == '6') { - exec_lua("auto_aux:go_right()"); + tree_go_right(); } else if (c == '4') { - exec_lua(format("auto_aux:go_left(%d)", sel)); + tree_go_left(); } else if (c == '9') { - exec_lua("auto_aux:scroll_up()"); + tree_scroll_up(); } else if (c == '3') { - exec_lua("auto_aux:scroll_down()"); + tree_scroll_down(); } else if (c == '7') { - exec_lua("auto_aux:scroll_left()"); + tree_scroll_left(); } else if (c == '1') { - exec_lua("auto_aux:scroll_right()"); + tree_scroll_right(); } else if (c == 'a') { - int s = automatizer_new_rule(); - if (s == -1) continue; - exec_lua(format("auto_aux:add_child('%s')", types_list[s])); + add_new_condition(rules[sel]); } else if (c == 'd') { @@ -476,12 +2964,12 @@ void do_cmd_automatizer() { int new_sel; - new_sel = exec_lua(format("return auto_aux:del_self(%d)", sel)); + new_sel = automatizer_del_self(sel); if ((sel != new_sel) && (new_sel >= 0)) { sel = new_sel; adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + adjust_current(sel); } else if (new_sel == -1) { @@ -493,30 +2981,84 @@ void do_cmd_automatizer() { active = ACTIVE_LIST; } - else if (c == 'x') - { - exec_lua("xml.display_english = not xml.display_english"); - } } } - /* Recalculate the rules */ - exec_lua("auto_aux.regen_ruleset()"); - screen_load(); } +static void easy_add_rule(action_type action, cptr mode, bool_ do_status, object_type *o_ptr) +{ + condition_type *condition = NULL; + + if (streq(mode, "tval")) + { + condition = condition_new_tval(o_ptr->tval); + } + else if (streq(mode, "tsval")) + { + condition_type *sval_condition = + condition_new_sval(o_ptr->sval, o_ptr->sval); + condition_type *tval_condition = + condition_new_tval(o_ptr->tval); + + condition = condition_new_and(); + condition_and_add(condition, tval_condition); + condition_and_add(condition, sval_condition); + } + else if (streq(mode, "name")) + { + char buf[128]; + object_desc(buf, o_ptr, -1, 0); + strlower(buf); + + condition = condition_new_name(buf); + } + + /* Use object status? */ + if (do_status == TRUE) + { + status_type status = object_status(o_ptr); + condition_type *status_condition = + condition_new_status(status); + condition_type *and_condition = + condition_new_and(); + + condition_and_add(and_condition, condition); + condition_and_add(and_condition, status_condition); + /* Replace condition */ + condition = and_condition; + } + + /* Build rule */ + { + static arule_type *rule = NULL; + /* Make rule */ + rule = rule_new(action_to_string(action), + action, + game_module_idx, + condition, + NULL); + + /* Append to list of rules */ + rules_append(rule); + } + + msg_print("Rule added. Please go to the Automatizer screen (press = then T)"); + msg_print("to save the modified ruleset."); +} + /* Add a new rule in an easy way */ bool_ automatizer_create = FALSE; void automatizer_add_rule(object_type *o_ptr, bool_ destroy) { char ch; bool_ do_status = FALSE; - cptr type = "destroy"; + action_type action = AUTO_DESTROY; if (!destroy) { - type = "pickup"; + action = AUTO_PICKUP; } while (TRUE) @@ -534,20 +3076,496 @@ void automatizer_add_rule(object_type *o_ptr, bool_ destroy) if (ch == 'T' || ch == 't') { - call_lua("easy_add_rule", "(s,s,d,O)", "", type, "tsval", do_status, o_ptr); + easy_add_rule(action, "tsval", do_status, o_ptr); break; } if (ch == 'F' || ch == 'f') { - call_lua("easy_add_rule", "(s,s,d,O)", "", type, "tval", do_status, o_ptr); + easy_add_rule(action, "tval", do_status, o_ptr); break; } if (ch == 'N' || ch == 'n') { - call_lua("easy_add_rule", "(s,s,d,O)", "", type, "name", do_status, o_ptr); + easy_add_rule(action, "name", do_status, o_ptr); break; } } } + +static condition_type *parse_condition(json_t *condition_json) +{ + cptr type_s = NULL; + match_type match; + + if ((condition_json == NULL) || json_is_null(condition_json)) + { + return NULL; + } + + if (json_unpack(condition_json, + "{s:s}", + "type", &type_s) < 0) + { + msg_print("Missing/invalid 'type' in condition"); + return NULL; + } + + if (!match_type_from_string(type_s, &match)) + { + msg_format("Invalid 'type' in condition: %s", type_s); + return NULL; + } + + switch (match) + { + case M_AND: + case M_OR: + { + json_t *conditions_j = json_object_get(condition_json, + "conditions"); + + if ((conditions_j == NULL) || + (json_is_null(conditions_j))) + { + return NULL; + } + else if (json_is_array(conditions_j)) + { + int i; + json_t *subcondition_j = NULL; + condition_type *condition = condition_new(match); + condition_type *subcondition = NULL; + + for (i = 0; i < json_array_size(conditions_j); i++) + { + subcondition_j = + json_array_get(conditions_j, i); + subcondition = + parse_condition(subcondition_j); + + if (subcondition != NULL) + { + condition_and_add(condition, subcondition); + } + } + + return condition; + } + else + { + msg_print("'conditions' property has invalid type"); + return NULL; + } + + break; + } + + case M_NOT: + case M_INVENTORY: + case M_EQUIPMENT: + { + json_t *condition_j = json_object_get(condition_json, + "condition"); + + if ((condition_j == NULL) || + (json_is_null(condition_j))) + { + return NULL; + } + else if (json_is_object(condition_j)) + { + condition_type *condition = + condition_new(match); + condition->subcondition = + parse_condition(condition_j); + return condition; + } + else + { + msg_print("Invlalid 'condition' property"); + return NULL; + } + } + + case M_NAME: + { + cptr s = NULL; + if (json_unpack(condition_json, + "{s:s}", + "name", &s) < 0) + { + msg_print("Missing/invalid 'name' property"); + return NULL; + } + + return condition_new_name(s); + } + + case M_CONTAIN: + { + cptr s = NULL; + if (json_unpack(condition_json, + "{s:s}", + "contain", &s) < 0) + { + msg_print("Missing/invalid 'contain' property"); + return NULL; + } + + return condition_new_contain(s); + } + + case M_SYMBOL: + { + cptr s = NULL; + int sl; + if (json_unpack(condition_json, "{s:s}", "symbol", &s) < 0) + { + msg_print("Missing/invalid 'symbol' property"); + return NULL; + } + + sl = strlen(s); + if (sl == 0) + { + msg_print("Invalid 'symbol' property: Too short"); + return NULL; + } + if (sl > 1) + { + msg_print("Invalid 'symbol' property: Too long"); + return NULL; + } + + return condition_new_symbol(s[0]); + } + + case M_INSCRIBED: + { + cptr s = NULL; + if (json_unpack(condition_json, "{s:s}", "inscription", &s) < 0) + { + msg_print("Missing/invalid 'inscription' property"); + return NULL; + } + + return condition_new_inscribed(s); + } + + case M_DISCOUNT: + { + int min, max; + + if (json_unpack(condition_json, "{s:i,s:i}", + "min", &min, + "max", &max) < 0) + { + msg_print("Missing/invalid 'min'/'max' properties"); + return NULL; + } + + return condition_new_discount(min, max); + } + + case M_TVAL: + { + int tval; + + if (json_unpack(condition_json, "{s:i}", "tval", &tval) < 0) + { + msg_print("Missing/invalid 'tval' property"); + return NULL; + } + + return condition_new_tval(tval); + } + + case M_SVAL: + { + int min, max; + + if (json_unpack(condition_json, "{s:i,s:i}", + "min", &min, + "max", &max) < 0) + { + msg_print("Missing/invalid 'min'/'max' properties"); + return NULL; + } + + return condition_new_sval(min, max); + } + + case M_STATUS: + { + cptr s; + status_type status; + + if (json_unpack(condition_json, "{s:s}", "status", &s) < 0) + { + msg_print("Missing/invalid 'status' property"); + return NULL; + } + + if (!status_from_string(s, &status)) + { + msg_format("Invalid 'status' property: %s", s); + return NULL; + } + + return condition_new_status(status); + } + + case M_STATE: + { + cptr s; + identification_state state; + + if (json_unpack(condition_json, "{s:s}", "state", &s) < 0) + { + msg_print("Missing/invalid 'state' property"); + return NULL; + } + + if (!identification_state_from_string(s, &state)) + { + msg_format("Invalid 'state' property: %s", s); + return NULL; + } + + return condition_new_state(state); + } + + case M_RACE: + { + cptr s; + + if (json_unpack(condition_json, "{s:s}", "race", &s) < 0) + { + msg_print("Missing/invalid 'race' property"); + return NULL; + } + + return condition_new_race(s); + } + + case M_SUBRACE: + { + cptr s; + + if (json_unpack(condition_json, "{s:s}", "subrace", &s) < 0) + { + msg_print("Missing/invalid 'subrace' property"); + return NULL; + } + + return condition_new_subrace(s); + } + + case M_CLASS: + { + cptr s; + + if (json_unpack(condition_json, "{s:s}", "class", &s) < 0) + { + msg_print("Missing/invalid 'class' property"); + return NULL; + } + + return condition_new_class(s); + } + + case M_LEVEL: + { + int min, max; + + if (json_unpack(condition_json, "{s:i,s:i}", + "min", &min, + "max", &max) < 0) + { + msg_print("Missing/invalid 'min'/'max' properties"); + return NULL; + } + + return condition_new_level(min, max); + } + + case M_SKILL: + { + cptr s; + s16b si; + int min, max; + + if (json_unpack(condition_json, "{s:i,s:i,s:s}", + "min", &min, + "max", &max, + "name", &s) < 0) + { + msg_print("Missing/invalid 'min'/'max'/'name' properties"); + return NULL; + } + + si = find_skill_i(s); + if (si < 0) + { + msg_print("Invalid 'name' property"); + return NULL; + } + + return condition_new_skill(min, max, si); + } + + case M_ABILITY: + { + cptr a; + s16b ai; + + if (json_unpack(condition_json, "{s:s}", + "ability", &a) < 0) + { + msg_print("Missing/invalid 'ability' property"); + return NULL; + } + + ai = find_ability(a); + if (ai < 0) + { + msg_print("Invalid 'ability' property"); + return NULL; + } + + return condition_new_ability(ai); + } + + } + + /* Could not parse */ + return NULL; +} + +static void parse_rule(json_t *rule_json) +{ + char *rule_name_s = NULL; + char *rule_action_s = NULL; + char *rule_module_s = NULL; + json_t *rule_inscription_j = NULL; + arule_type *rule = NULL; + action_type action; + int module_idx; + + if (!json_is_object(rule_json)) + { + msg_print("Rule is not an object"); + return; + } + + /* Retrieve the attributes */ + if (json_unpack(rule_json, + "{s:s,s:s,s:s}", + "name", &rule_name_s, + "action", &rule_action_s, + "module", &rule_module_s) < 0) + { + msg_print("Rule missing required field(s)"); + return; + } + + /* Get the optional inscription */ + rule_inscription_j = json_object_get(rule_json, "inscription"); + + /* Convert attributes */ + if (!action_from_string((cptr) rule_action_s, &action)) + { + msg_format("Invalid rule action '%s'", rule_action_s); + return; + } + + module_idx = find_module((cptr) rule_module_s); + if (module_idx < 0) + { + msg_format("Skipping rule for unrecognized module '%s'", + (cptr) rule_module_s); + return; + } + + /* Sanity check: Inscription */ + if (action == AUTO_INSCRIBE) + { + if (rule_inscription_j == NULL) + { + msg_print("Inscription rule missing 'inscription' attribute"); + return; + } + if (!json_is_string(rule_inscription_j)) + { + msg_print("Inscription rule 'inscription' attribute wrong type"); + return; + } + } + + /* Create rule */ + rule = rule_new(rule_name_s, + action, + module_idx, + NULL, + json_string_value(rule_inscription_j)); + rules_append(rule); + + /* Parse the conditions */ + rule->condition = parse_condition(json_object_get(rule_json, "condition")); +} + +static void parse_rules(json_t *rules) +{ + int i; + + if (!json_is_array(rules)) + { + msg_format("Error 'rules' is not an array"); + return; + } + + for (i = 0; i < json_array_size(rules); i++) + { + json_t *rule = json_array_get(rules, i); + parse_rule(rule); + } +} + +/** + * Initialize the automatizer. This function may be called multiple + * times with different file names -- it should NOT clear any + * automatizer state (including loaded rules). + */ +void automatizer_init(cptr file_path) +{ + json_t *rules_json = NULL; + json_error_t error; + + /* Does the file exist? */ + if (!file_exist(file_path)) + { + /* No big deal, we'll just skip */ + goto out; + } + + /* Parse file */ + rules_json = json_load_file(file_path, 0, &error); + if (rules_json == NULL) + { + msg_format("Error parsing automatizer rules from '%s'.", file_path); + msg_format("Line %d, Column %d", error.line, error.column); + msg_print(NULL); + goto out; + } + + /* Go through all the found rules */ + parse_rules(rules_json); + +out: + if (rules_json == NULL) + { + json_decref(rules_json); + } +} -- cgit v1.2.3