From 8497700fa0c3824e95e59537530dc0f53e500536 Mon Sep 17 00:00:00 2001 From: dark-ether Date: Mon, 18 Jul 2022 16:01:33 -0300 Subject: [PATCH 1/3] server/ruleset.c: Moved action related logic to their own section, due to not being too much work added logic to load action related ruleset configuration in either game.ruleset or actions.ruleset --- server/ruleset.c | 643 +++++++++++++++++++++++++---------------------- 1 file changed, 342 insertions(+), 301 deletions(-) diff --git a/server/ruleset.c b/server/ruleset.c index 32b1d3ba85..77a5f3adc1 100644 --- a/server/ruleset.c +++ b/server/ruleset.c @@ -6847,289 +6847,7 @@ static bool load_ruleset_game(struct section_file *file, bool act, RS_MAX_INCITE_TOTAL_FCT, "incite_cost.total_factor"); - if (ok) { - /* Auto attack. */ - struct action_auto_perf *auto_perf; - - /* Action auto performers aren't ready to be exposed in the ruleset - * yet. The behavior when two action auto performers for the same - * cause can fire isn't set in stone yet. How is one of them chosen? - * What if all the actions of the chosen action auto performer turned - * out to be illegal but one of the other action auto performers that - * fired has legal actions? These issues can decide what other action - * rules action auto performers can represent in the future. Deciding - * should therefore wait until a rule needs action auto performers to - * work a certain way. */ - /* Only one action auto performer, ACTION_AUTO_MOVED_ADJ, is caused - * by AAPC_UNIT_MOVED_ADJ. It is therefore safe to expose the full - * requirement vector to the ruleset. */ - struct requirement_vector *reqs; - - /* A unit moved next to this unit and the autoattack server setting - * is enabled. */ - auto_perf = action_auto_perf_slot_number(ACTION_AUTO_MOVED_ADJ); - auto_perf->cause = AAPC_UNIT_MOVED_ADJ; - - reqs = lookup_req_list(file, compat, - "auto_attack", "if_attacker", - "auto_attack"); - if (reqs == NULL) { - ok = FALSE; - } - - requirement_vector_copy(&auto_perf->reqs, reqs); - - if (!load_action_auto_actions(file, auto_perf, - "auto_attack.attack_actions", - filename)) { - /* Failed to load auto attack actions */ - ruleset_error(LOG_ERROR, - "\"%s\": %s: failed load %s.", - filename, "auto_attack", "attack_actions"); - ok = FALSE; - } - } - - /* section: actions */ - if (ok) { - action_iterate(act_id) { - struct action *paction = action_by_number(act_id); - - if (!load_action_blocked_by_list(file, filename, paction)) { - ok = FALSE; - } - } action_iterate_end; - - if (!lookup_bv_actions(file, filename, - &game.info.diplchance_initial_odds, - "actions.diplchance_initial_odds")) { - ok = FALSE; - } - - /* If the "Poison City" action or the "Poison City Escape" action - * should empty the granary. */ - /* TODO: empty granary and reduce population should become separate - * action effect flags when actions are generalized. */ - game.info.poison_empties_food_stock - = secfile_lookup_bool_default(file, - RS_DEFAULT_POISON_EMPTIES_FOOD_STOCK, - "actions.poison_empties_food_stock"); - - /* If the "Steal Maps" action or the "Steal Maps Escape" action always - * will reveal all cities when successful. */ - game.info.steal_maps_reveals_all_cities - = secfile_lookup_bool_default(file, - RS_DEFAULT_STEAL_MAP_REVEALS_CITIES, - "actions.steal_maps_reveals_all_cities"); - - /* Allow setting certain properties for some actions before - * generalized actions. */ - action_iterate(act_id) { - if (!load_action_range(file, act_id)) { - ok = FALSE; - } - if (!load_action_kind(file, act_id)) { - ok = FALSE; - } - if (!load_action_actor_consuming_always(file, act_id)) { - ok = FALSE; - } - load_action_ui_name(file, act_id, - action_ui_name_ruleset_var_name(act_id)); - } action_iterate_end; - - /* The quiet (don't auto generate help for) property of all actions - * live in a single enum vector. This avoids generic action - * expectations. */ - if (secfile_entry_by_path(file, "actions.quiet_actions")) { - enum gen_action *quiet_actions; - size_t asize; - int j; - - quiet_actions = - secfile_lookup_enum_vec(file, &asize, gen_action, - "actions.quiet_actions"); - - if (!quiet_actions) { - /* Entity exists but couldn't read it. */ - ruleset_error(LOG_ERROR, - "\"%s\": actions.quiet_actions: bad action list", - filename); - - ok = FALSE; - } - - for (j = 0; j < asize; j++) { - /* Don't auto generate help text for this action. */ - action_by_number(quiet_actions[j])->quiet = TRUE; - } - - free(quiet_actions); - } - - /* Hard code action sub results for now. */ - - /* Unit Enter Hut */ - action_by_result_iterate(paction, act_id, ACTRES_HUT_ENTER) { - BV_SET(paction->sub_results, ACT_SUB_RES_HUT_ENTER); - } action_by_result_iterate_end; - BV_SET(action_by_number(ACTION_PARADROP_ENTER)->sub_results, - ACT_SUB_RES_HUT_ENTER); - BV_SET(action_by_number(ACTION_PARADROP_ENTER_CONQUER)->sub_results, - ACT_SUB_RES_HUT_ENTER); - - /* Unit Frighten Hut */ - action_by_result_iterate(paction, act_id, ACTRES_HUT_FRIGHTEN) { - BV_SET(paction->sub_results, ACT_SUB_RES_HUT_FRIGHTEN); - } action_by_result_iterate_end; - BV_SET(action_by_number(ACTION_PARADROP_FRIGHTEN)->sub_results, - ACT_SUB_RES_HUT_FRIGHTEN); - BV_SET(action_by_number(ACTION_PARADROP_FRIGHTEN_CONQUER)->sub_results, - ACT_SUB_RES_HUT_FRIGHTEN); - - /* Unit May Embark */ - action_iterate(act_id) { - struct action *paction = action_by_number(act_id); - - if (secfile_lookup_bool_default(file, FALSE, - "civstyle.paradrop_to_transport") - && (action_has_result(paction, ACTRES_PARADROP) - || action_has_result(paction, ACTRES_PARADROP_CONQUER))) { - BV_SET(paction->sub_results, ACT_SUB_RES_MAY_EMBARK); - } - - /* Embark actions will always embark, not maybe embark. */ - } action_iterate_end; - - /* Non Lethal bombard */ - BV_SET(action_by_number(ACTION_BOMBARD)->sub_results, - ACT_SUB_RES_NON_LETHAL); - BV_SET(action_by_number(ACTION_BOMBARD2)->sub_results, - ACT_SUB_RES_NON_LETHAL); - BV_SET(action_by_number(ACTION_BOMBARD3)->sub_results, - ACT_SUB_RES_NON_LETHAL); - } - - if (ok) { - /* Forced actions after another action was successfully performed. */ - - /* "Bribe Unit" */ - if (!load_action_post_success_force(file, filename, - ACTION_AUTO_POST_BRIBE, - action_by_number( - ACTION_SPY_BRIBE_UNIT))) { - ok = FALSE; - } - - /* "Attack" */ - if (!load_action_post_success_force(file, filename, - ACTION_AUTO_POST_ATTACK, - action_by_number( - ACTION_ATTACK))) { - ok = FALSE; - } - - /* "Wipe Units" */ - if (!load_action_post_success_force(file, filename, - ACTION_AUTO_POST_WIPE_UNITS, - action_by_number( - ACTION_WIPE_UNITS))) { - ok = FALSE; - } - - /* No "Suicide Attack". Can't act when dead. */ - } - - if (ok) { - struct action_auto_perf *auto_perf; - - /* The city that made the unit's current tile native is gone. - * Evaluated against an adjacent tile. */ - auto_perf = action_auto_perf_slot_number(ACTION_AUTO_ESCAPE_CITY); - auto_perf->cause = AAPC_CITY_GONE; - - /* I have no objections to moving this out of game's actions to - * cities.ruleset, units.ruleset or an other location in game.ruleset - * you find more suitable. -- Sveinung */ - if (!load_action_auto_actions(file, auto_perf, - "actions.escape_city", filename)) { - ok = FALSE; - } - } - - if (ok) { - struct action_auto_perf *auto_perf; - - /* The unit's stack has been defeated and is scheduled for execution - * but the unit has the CanEscape unit type flag. - * Evaluated against an adjacent tile. */ - auto_perf = action_auto_perf_slot_number(ACTION_AUTO_ESCAPE_STACK); - auto_perf->cause = AAPC_UNIT_STACK_DEATH; - /* I have no objections to moving this out of game's actions to - * cities.ruleset, units.ruleset or an other location in game.ruleset - * you find more suitable. -- Sveinung */ - if (!load_action_auto_actions(file, auto_perf, - "actions.unit_stack_death", filename)) { - ok = FALSE; - } - } - - if (ok) { - sec = secfile_sections_by_name_prefix(file, - ACTION_ENABLER_SECTION_PREFIX); - - if (sec) { - section_list_iterate(sec, psection) { - struct action_enabler *enabler; - const char *sec_name = section_name(psection); - struct action *paction; - struct requirement_vector *actor_reqs; - struct requirement_vector *target_reqs; - const char *action_text; - - enabler = action_enabler_new(); - - action_text = secfile_lookup_str(file, "%s.action", sec_name); - - if (action_text == NULL) { - ruleset_error(LOG_ERROR, "\"%s\" [%s] missing action to enable.", - filename, sec_name); - ok = FALSE; - break; - } - - paction = action_by_rule_name(action_text); - if (!paction) { - ruleset_error(LOG_ERROR, "\"%s\" [%s] lists unknown action type \"%s\".", - filename, sec_name, action_text); - ok = FALSE; - break; - } - - enabler->action = paction->id; - - actor_reqs = lookup_req_list(file, compat, sec_name, "actor_reqs", action_text); - if (actor_reqs == NULL) { - ok = FALSE; - break; - } - - requirement_vector_copy(&enabler->actor_reqs, actor_reqs); - - target_reqs = lookup_req_list(file, compat, sec_name, "target_reqs", action_text); - if (target_reqs == NULL) { - ok = FALSE; - break; - } - - requirement_vector_copy(&enabler->target_reqs, target_reqs); - - action_enabler_add(enabler); - } section_list_iterate_end; - section_list_destroy(sec); - } - } } if (ok) { @@ -7734,8 +7452,299 @@ static bool load_ruleset_game(struct section_file *file, bool act, static bool load_ruleset_actions(struct section_file *file, struct rscompat_info *compat) { - /* For now no code is here */ - return TRUE; + bool ok = TRUE; + const char *filename; + struct section_list *sec; + + if (file == NULL) { + return FALSE; + } + filename = secfile_name(file); + + if (ok) { + /* Auto attack. */ + struct action_auto_perf *auto_perf; + + /* Action auto performers aren't ready to be exposed in the ruleset + * yet. The behavior when two action auto performers for the same + * cause can fire isn't set in stone yet. How is one of them chosen? + * What if all the actions of the chosen action auto performer turned + * out to be illegal but one of the other action auto performers that + * fired has legal actions? These issues can decide what other action + * rules action auto performers can represent in the future. Deciding + * should therefore wait until a rule needs action auto performers to + * work a certain way. */ + /* Only one action auto performer, ACTION_AUTO_MOVED_ADJ, is caused + * by AAPC_UNIT_MOVED_ADJ. It is therefore safe to expose the full + * requirement vector to the ruleset. */ + struct requirement_vector *reqs; + + /* A unit moved next to this unit and the autoattack server setting + * is enabled. */ + auto_perf = action_auto_perf_slot_number(ACTION_AUTO_MOVED_ADJ); + auto_perf->cause = AAPC_UNIT_MOVED_ADJ; + + reqs = lookup_req_list(file, compat, + "auto_attack", "if_attacker", + "auto_attack"); + if (reqs == NULL) { + ok = FALSE; + } + + requirement_vector_copy(&auto_perf->reqs, reqs); + + if (!load_action_auto_actions(file, auto_perf, + "auto_attack.attack_actions", + filename)) { + /* Failed to load auto attack actions */ + ruleset_error(LOG_ERROR, + "\"%s\": %s: failed load %s.", + filename, "auto_attack", "attack_actions"); + ok = FALSE; + } + } + + /* section: actions */ + if (ok) { + action_iterate(act_id) { + struct action *paction = action_by_number(act_id); + + if (!load_action_blocked_by_list(file, filename, paction)) { + ok = FALSE; + } + } action_iterate_end; + + if (!lookup_bv_actions(file, filename, + &game.info.diplchance_initial_odds, + "actions.diplchance_initial_odds")) { + ok = FALSE; + } + + /* If the "Poison City" action or the "Poison City Escape" action + * should empty the granary. */ + /* TODO: empty granary and reduce population should become separate + * action effect flags when actions are generalized. */ + game.info.poison_empties_food_stock + = secfile_lookup_bool_default(file, + RS_DEFAULT_POISON_EMPTIES_FOOD_STOCK, + "actions.poison_empties_food_stock"); + + /* If the "Steal Maps" action or the "Steal Maps Escape" action always + * will reveal all cities when successful. */ + game.info.steal_maps_reveals_all_cities + = secfile_lookup_bool_default(file, + RS_DEFAULT_STEAL_MAP_REVEALS_CITIES, + "actions.steal_maps_reveals_all_cities"); + + /* Allow setting certain properties for some actions before + * generalized actions. */ + action_iterate(act_id) { + if (!load_action_range(file, act_id)) { + ok = FALSE; + } + if (!load_action_kind(file, act_id)) { + ok = FALSE; + } + if (!load_action_actor_consuming_always(file, act_id)) { + ok = FALSE; + } + load_action_ui_name(file, act_id, + action_ui_name_ruleset_var_name(act_id)); + } action_iterate_end; + + /* The quiet (don't auto generate help for) property of all actions + * live in a single enum vector. This avoids generic action + * expectations. */ + if (secfile_entry_by_path(file, "actions.quiet_actions")) { + enum gen_action *quiet_actions; + size_t asize; + int j; + + quiet_actions = + secfile_lookup_enum_vec(file, &asize, gen_action, + "actions.quiet_actions"); + + if (!quiet_actions) { + /* Entity exists but couldn't read it. */ + ruleset_error(LOG_ERROR, + "\"%s\": actions.quiet_actions: bad action list", + filename); + + ok = FALSE; + } + + for (j = 0; j < asize; j++) { + /* Don't auto generate help text for this action. */ + action_by_number(quiet_actions[j])->quiet = TRUE; + } + + free(quiet_actions); + } + + /* Hard code action sub results for now. */ + + /* Unit Enter Hut */ + action_by_result_iterate(paction, act_id, ACTRES_HUT_ENTER) { + BV_SET(paction->sub_results, ACT_SUB_RES_HUT_ENTER); + } action_by_result_iterate_end; + BV_SET(action_by_number(ACTION_PARADROP_ENTER)->sub_results, + ACT_SUB_RES_HUT_ENTER); + BV_SET(action_by_number(ACTION_PARADROP_ENTER_CONQUER)->sub_results, + ACT_SUB_RES_HUT_ENTER); + + /* Unit Frighten Hut */ + action_by_result_iterate(paction, act_id, ACTRES_HUT_FRIGHTEN) { + BV_SET(paction->sub_results, ACT_SUB_RES_HUT_FRIGHTEN); + } action_by_result_iterate_end; + BV_SET(action_by_number(ACTION_PARADROP_FRIGHTEN)->sub_results, + ACT_SUB_RES_HUT_FRIGHTEN); + BV_SET(action_by_number(ACTION_PARADROP_FRIGHTEN_CONQUER)->sub_results, + ACT_SUB_RES_HUT_FRIGHTEN); + + /* Unit May Embark */ + action_iterate(act_id) { + struct action *paction = action_by_number(act_id); + + if (secfile_lookup_bool_default(file, FALSE, + "civstyle.paradrop_to_transport") + && (action_has_result(paction, ACTRES_PARADROP) + || action_has_result(paction, ACTRES_PARADROP_CONQUER))) { + BV_SET(paction->sub_results, ACT_SUB_RES_MAY_EMBARK); + } + + /* Embark actions will always embark, not maybe embark. */ + } action_iterate_end; + + /* Non Lethal bombard */ + BV_SET(action_by_number(ACTION_BOMBARD)->sub_results, + ACT_SUB_RES_NON_LETHAL); + BV_SET(action_by_number(ACTION_BOMBARD2)->sub_results, + ACT_SUB_RES_NON_LETHAL); + BV_SET(action_by_number(ACTION_BOMBARD3)->sub_results, + ACT_SUB_RES_NON_LETHAL); + } + + if (ok) { + /* Forced actions after another action was successfully performed. */ + + /* "Bribe Unit" */ + if (!load_action_post_success_force(file, filename, + ACTION_AUTO_POST_BRIBE, + action_by_number( + ACTION_SPY_BRIBE_UNIT))) { + ok = FALSE; + } + + /* "Attack" */ + if (!load_action_post_success_force(file, filename, + ACTION_AUTO_POST_ATTACK, + action_by_number( + ACTION_ATTACK))) { + ok = FALSE; + } + + /* "Wipe Units" */ + if (!load_action_post_success_force(file, filename, + ACTION_AUTO_POST_WIPE_UNITS, + action_by_number( + ACTION_WIPE_UNITS))) { + ok = FALSE; + } + + /* No "Suicide Attack". Can't act when dead. */ + } + + if (ok) { + struct action_auto_perf *auto_perf; + + /* The city that made the unit's current tile native is gone. + * Evaluated against an adjacent tile. */ + auto_perf = action_auto_perf_slot_number(ACTION_AUTO_ESCAPE_CITY); + auto_perf->cause = AAPC_CITY_GONE; + + /* I have no objections to moving this out of game's actions to + * cities.ruleset, units.ruleset or an other location in game.ruleset + * you find more suitable. -- Sveinung */ + if (!load_action_auto_actions(file, auto_perf, + "actions.escape_city", filename)) { + ok = FALSE; + } + } + + if (ok) { + struct action_auto_perf *auto_perf; + + /* The unit's stack has been defeated and is scheduled for execution + * but the unit has the CanEscape unit type flag. + * Evaluated against an adjacent tile. */ + auto_perf = action_auto_perf_slot_number(ACTION_AUTO_ESCAPE_STACK); + auto_perf->cause = AAPC_UNIT_STACK_DEATH; + + /* I have no objections to moving this out of game's actions to + * cities.ruleset, units.ruleset or an other location in game.ruleset + * you find more suitable. -- Sveinung */ + if (!load_action_auto_actions(file, auto_perf, + "actions.unit_stack_death", filename)) { + ok = FALSE; + } + } + + if (ok) { + sec = secfile_sections_by_name_prefix(file, + ACTION_ENABLER_SECTION_PREFIX); + + if (sec) { + section_list_iterate(sec, psection) { + struct action_enabler *enabler; + const char *sec_name = section_name(psection); + struct action *paction; + struct requirement_vector *actor_reqs; + struct requirement_vector *target_reqs; + const char *action_text; + + enabler = action_enabler_new(); + + action_text = secfile_lookup_str(file, "%s.action", sec_name); + + if (action_text == NULL) { + ruleset_error(LOG_ERROR, "\"%s\" [%s] missing action to enable.", + filename, sec_name); + ok = FALSE; + break; + } + + paction = action_by_rule_name(action_text); + if (!paction) { + ruleset_error(LOG_ERROR, "\"%s\" [%s] lists unknown action type \"%s\".", + filename, sec_name, action_text); + ok = FALSE; + break; + } + + enabler->action = paction->id; + + actor_reqs = lookup_req_list(file, compat, sec_name, "actor_reqs", action_text); + if (actor_reqs == NULL) { + ok = FALSE; + break; + } + + requirement_vector_copy(&enabler->actor_reqs, actor_reqs); + + target_reqs = lookup_req_list(file, compat, sec_name, "target_reqs", action_text); + if (target_reqs == NULL) { + ok = FALSE; + break; + } + + requirement_vector_copy(&enabler->target_reqs, target_reqs); + + action_enabler_add(enabler); + } section_list_iterate_end; + section_list_destroy(sec); + } + } + return ok; } /**********************************************************************//** @@ -9080,7 +9089,6 @@ static bool load_rulesetdir(const char *rsdir, bool compat_mode, { struct section_file *techfile, *unitfile, *buildfile, *govfile, *terrfile; struct section_file *stylefile, *cityfile, *nationfile, *effectfile, *gamefile; - struct section_file *actionfile; bool ok = TRUE; struct rscompat_info compat_info; @@ -9150,26 +9158,59 @@ static bool load_rulesetdir(const char *rsdir, bool compat_mode, } if (ok) { - /* Can only happen here because 3.1 rulesets may not have a - * actions.ruleset. remember to in 3.3 move it with the others. */ - if (compat_info.version < RSFORMAT_3_2) { - if (!compat_info.compat_mode) { - ok = FALSE; - ruleset_error(LOG_ERROR, "Tried to load ruleset of earlier version without compatibility mode."); + /* TODO: in 3.3 stop supporting actions section, auto attack section + * and action enablers in game.ruleset */ + struct section_file *actionfile; + bool actions_section, autoattack_section, enabler_sections; + bool noneInGame; + + actions_section = secfile_section_by_name(gamefile, "actions") == NULL; + autoattack_section = secfile_section_by_name(gamefile, "auto_attack") + == NULL; + enabler_sections = secfile_sections_by_name_prefix(gamefile, + ACTION_ENABLER_SECTION_PREFIX) == NULL; + /* 2 things why does this return NULL on 0 found and is that the + * correct form of indenting? doing it the normal way would pass the + * cap of 76 + newline per line*/ + noneInGame = actions_section && autoattack_section && enabler_sections; + + if (!noneInGame && actions_section) { + ruleset_error(LOG_ERROR, "Actions section not present in " + "game.ruleset but either enablers or auto attack " + "section present. You must move all of them " + "simultaneously to actions.ruleset"); + ok = FALSE; + } + if (!noneInGame && autoattack_section) { + ruleset_error(LOG_ERROR, "Auto Attack section not present in " + "game.ruleset but either enablers or Actions section" + "present. You must move all of them simultaneously to" + "actions.ruleset"); + ok = FALSE; + } + if (!noneInGame && enabler_sections) { + ruleset_error(LOG_ERROR, "Action Enablers not present in " + "game.ruleset but either Actions section or auto attack" + "section present. You must move all of them " + "simultaneously to actions.ruleset"); + ok = FALSE; + } + + if (ok) { + if (noneInGame) { + /* It may seem strange to check only if all don't exist, my + * logic is that we don't want to force loading from game.ruleset + * even if we are in compat mode. Because if we do that then if we + * enter compat mode by mistake a correct ruleset can't load. And + * if the version is lower than RSFORMAT_3_2 the related sections + * will be inside game.ruleset anyway so no need to check */ + actionfile = openload_ruleset_file("actions", rsdir); + load_ruleset_actions(actionfile, &compat_info); + nullcheck_secfile_destroy(actionfile); } else { load_ruleset_actions(gamefile, &compat_info); } } - else { - actionfile = openload_ruleset_file("actions", rsdir); - if (actionfile == NULL) { - ok = FALSE; - } - if (ok) { - /* It may be necessary to further divide load_ruleset_actions in a load_action_names. */ - ok = load_ruleset_actions(actionfile, &compat_info); - } - } } if (ok) { -- 2.37.1