diff --git a/config/fxdata/creature.cfg b/config/fxdata/creature.cfg index 7c83edccb4..5b04800cee 100644 --- a/config/fxdata/creature.cfg +++ b/config/fxdata/creature.cfg @@ -70,6 +70,11 @@ RangeMax = 0 ; 3,4 will only target other players, 5,6 only own player. ; 7 will target traps. PrimaryTarget = 0 +; Instance used by creature "going postal" while working in a room +; Creature will only use available instances with the highest priority, +; If multiple instances have the same PostalPriority, one is chosen randomly +; 0 disables the instance for beeing used while "going postal". +PostalPriority = 0 ; Instance properties flags: ; REPEAT_TRIGGER allows player to hold down the mouse button to cast. ; DISPLAY_SWIPE shows the swipe in possession loaded from the creatures 'PossessSwipeIndex'. @@ -81,7 +86,6 @@ PrimaryTarget = 0 ; SELF_BUFF can be applied to caster. ; RANGED_BUFF can be applied to another friendly creature. ; NEEDS_TARGET Cannot be used in possession without a target to cast it on. -; QUICK allows the instance to be used by creatures going postal. ; DISARMING allows the instance to be used against traps. Properties = ; Function used as the instance action, and its parameters. @@ -181,7 +185,8 @@ Graphics = RANGEDATTACK RangeMin = 156 RangeMax = MAX PrimaryTarget = 3 -Properties = DESTRUCTIVE QUICK RANGED_ATTACK +PostalPriority = 16 +Properties = DESTRUCTIVE RANGED_ATTACK Function = creature_fire_shot SHOT_ARROW 0 [instance5] @@ -200,7 +205,8 @@ Graphics = RANGEDATTACK RangeMin = 156 RangeMax = MAX PrimaryTarget = 3 -Properties = DESTRUCTIVE QUICK RANGED_ATTACK +PostalPriority = 18 +Properties = DESTRUCTIVE RANGED_ATTACK Function = creature_fire_shot SHOT_FIREBALL 0 [instance6] @@ -276,7 +282,8 @@ Graphics = RANGEDATTACK RangeMin = 768 RangeMax = 5120 PrimaryTarget = 3 -Properties = DESTRUCTIVE QUICK RANGED_ATTACK +PostalPriority = 10 +Properties = DESTRUCTIVE RANGED_ATTACK Function = creature_fire_shot SHOT_LIGHTNING 0 [instance10] @@ -333,7 +340,8 @@ Graphics = RANGEDATTACK RangeMin = 156 RangeMax = MAX PrimaryTarget = 4 -Properties = QUICK RANGED_ATTACK +PostalPriority = 2 +Properties = RANGED_ATTACK Function = creature_cast_spell SPELL_POISON_CLOUD 0 [instance13] @@ -464,7 +472,8 @@ Graphics = RANGEDATTACK RangeMin = 156 RangeMax = MAX PrimaryTarget = 3 -Properties = DESTRUCTIVE QUICK RANGED_ATTACK +PostalPriority = 14 +Properties = DESTRUCTIVE RANGED_ATTACK Function = creature_fire_shot SHOT_MISSILE 0 [instance20] @@ -483,7 +492,8 @@ Graphics = RANGEDATTACK RangeMin = 156 RangeMax = MAX PrimaryTarget = 3 -Properties = DESTRUCTIVE QUICK RANGED_ATTACK +PostalPriority = 0 +Properties = DESTRUCTIVE RANGED_ATTACK Function = creature_fire_shot SHOT_NAVI_MISSILE 0 [instance21] @@ -594,7 +604,8 @@ TooltipTextID = 234 SymbolSprites = 440 Graphics = RANGEDATTACK PrimaryTarget = 3 -Properties = DANGEROUS DESTRUCTIVE QUICK RANGED_ATTACK +PostalPriority = 4 +Properties = DANGEROUS DESTRUCTIVE RANGED_ATTACK Function = creature_fire_shot SHOT_GRENADE 0 [instance27] @@ -613,7 +624,8 @@ Graphics = RANGEDATTACK RangeMin = 156 RangeMax = MAX PrimaryTarget = 3 -Properties = DESTRUCTIVE QUICK RANGED_ATTACK MELEE_ATTACK +PostalPriority = 8 +Properties = DESTRUCTIVE RANGED_ATTACK MELEE_ATTACK Function = creature_cast_spell SPELL_HAILSTORM 0 [instance28] @@ -831,7 +843,8 @@ Graphics = RANGEDATTACK RangeMin = 1000 RangeMax = MAX PrimaryTarget = 3 -Properties = DANGEROUS DESTRUCTIVE QUICK RANGED_ATTACK +PostalPriority = 6 +Properties = DANGEROUS DESTRUCTIVE RANGED_ATTACK Function = creature_fire_shot SHOT_LIZARD 0 [instance41] diff --git a/src/config_creature.c b/src/config_creature.c index 8a1cccbb3d..6128f8812b 100644 --- a/src/config_creature.c +++ b/src/config_creature.c @@ -97,6 +97,7 @@ const struct NamedCommand creaturetype_instance_commands[] = { {"ValidateSourceFunc", 18}, {"ValidateTargetFunc", 19}, {"SearchTargetsFunc", 20}, + {"PostalPriority", 21}, {NULL, 0}, }; @@ -108,7 +109,6 @@ const struct NamedCommand creaturetype_instance_properties[] = { {"SELF_BUFF", InstPF_SelfBuff}, {"DANGEROUS", InstPF_Dangerous}, {"DESTRUCTIVE", InstPF_Destructive}, - {"QUICK", InstPF_Quick}, {"DISARMING", InstPF_Disarming}, {"DISPLAY_SWIPE", InstPF_UsesSwipe}, {"RANGED_BUFF", InstPF_RangedBuff}, @@ -841,6 +841,7 @@ TbBool parse_creaturetype_instance_blocks(char *buf, long len, const char *confi inst_inf->validate_target_func = 0; inst_inf->validate_target_func_params[0] = 0; inst_inf->validate_target_func_params[1] = 0; + inst_inf->postal_priority = 0; } } instance_desc[INSTANCE_TYPES_MAX - 1].name = NULL; // must be null for get_id @@ -1212,6 +1213,19 @@ TbBool parse_creaturetype_instance_blocks(char *buf, long len, const char *confi } } break; + case 21: // Postal Instance priority + if (get_conf_parameter_single(buf, &pos, len, word_buf, sizeof(word_buf)) > 0) + { + k = atoi(word_buf); + inst_inf->postal_priority = k; + n++; + } + if (n < 1) + { + CONFWRNLOG("Couldn't read \"%s\" parameter in [%.*s] block of %s file.", + COMMAND_TEXT(cmd_num), blocknamelen, blockname, config_textname); + } + break; case ccr_comment: break; case ccr_endOfFile: @@ -1693,6 +1707,7 @@ TbBool load_creaturetypes_config_file(const char *textname, const char *fname, u game.conf.magic_conf.instance_info[i].reset_time = 0; game.conf.magic_conf.instance_info[i].fp_reset_time = 0; game.conf.magic_conf.instance_info[i].graphics_idx = 0; + game.conf.magic_conf.instance_info[i].postal_priority = 0; game.conf.magic_conf.instance_info[i].instance_property_flags = 0; game.conf.magic_conf.instance_info[i].force_visibility = 0; game.conf.magic_conf.instance_info[i].primary_target = 0; diff --git a/src/config_creature.h b/src/config_creature.h index b59c7e3623..cf0cafc576 100644 --- a/src/config_creature.h +++ b/src/config_creature.h @@ -147,7 +147,7 @@ enum InstancePropertiesFlags { InstPF_RangedDebuff = 0x0010, InstPF_Dangerous = 0x0020, InstPF_Destructive = 0x0040, - InstPF_Quick = 0x0080, + InstPF_Unused = 0x0080, //Quick InstPF_Disarming = 0x0100, InstPF_UsesSwipe = 0x0200, InstPF_RangedBuff = 0x0400, diff --git a/src/creature_instances.c b/src/creature_instances.c index ddbc99e38d..cd003c34b4 100644 --- a/src/creature_instances.c +++ b/src/creature_instances.c @@ -327,10 +327,15 @@ TbBool instance_is_ranged_weapon_vs_objects(CrInstance inum) return (((inst_inf->instance_property_flags & InstPF_RangedAttack) != 0) && ((inst_inf->instance_property_flags & InstPF_Destructive) != 0) && !(inst_inf->instance_property_flags & InstPF_Dangerous)); } -TbBool instance_is_quick_range_weapon(CrInstance inum) +/** + * Informs whether the creature has an instance which can be used when going postal. + * Going Postal is the behavior where creatures attack others at their job, like warlocks in the library + * @return True if it has a postal_priority value > 0. + */ +TbBool instance_is_used_for_going_postal(CrInstance inum) { struct InstanceInfo* inst_inf = creature_instance_info_get(inum); - return (((inst_inf->instance_property_flags & InstPF_RangedAttack) != 0) && ((inst_inf->instance_property_flags & InstPF_Quick) != 0)); + return (inst_inf->postal_priority > 0); } TbBool instance_is_melee_attack(CrInstance inum) @@ -402,7 +407,7 @@ TbBool creature_has_ranged_object_weapon(const struct Thing *creatng) return false; } -TbBool creature_has_quick_range_weapon(const struct Thing *creatng) +TbBool creature_has_weapon_for_postal(const struct Thing *creatng) { TRACE_THING(creatng); const struct CreatureControl* cctrl = creature_control_get_from_thing(creatng); @@ -410,7 +415,7 @@ TbBool creature_has_quick_range_weapon(const struct Thing *creatng) { if (cctrl->instance_available[inum]) { - if (instance_is_quick_range_weapon(inum)) + if (instance_is_used_for_going_postal(inum)) return true; } } diff --git a/src/creature_instances.h b/src/creature_instances.h index 3db3c3b09e..baf2d32e56 100644 --- a/src/creature_instances.h +++ b/src/creature_instances.h @@ -105,6 +105,7 @@ struct InstanceInfo { long reset_time; long fp_reset_time; unsigned char graphics_idx; + char postal_priority; short instance_property_flags; short force_visibility; unsigned char primary_target; @@ -150,7 +151,7 @@ void creature_increase_available_instances(struct Thing *thing); TbBool creature_has_ranged_weapon(const struct Thing *thing); TbBool creature_has_disarming_weapon(const struct Thing* creatng); TbBool creature_has_ranged_object_weapon(const struct Thing *creatng); -TbBool creature_has_quick_range_weapon(const struct Thing *creatng); +TbBool creature_has_weapon_for_postal(const struct Thing *creatng); TbBool creature_has_melee_attack(const struct Thing *creatng); int creature_instance_get_available_pos_for_id(struct Thing *thing, CrInstance req_inst_id); diff --git a/src/creature_states_combt.c b/src/creature_states_combt.c index 6dc8f8a95b..f2be3d7b79 100644 --- a/src/creature_states_combt.c +++ b/src/creature_states_combt.c @@ -1781,16 +1781,6 @@ long ranged_combat_move(struct Thing *thing, struct Thing *enmtng, MapCoordDelta return thing_in_field_of_view(thing, enmtng); } -#define INSTANCE_RET_IF_AVAIL(thing, inst_id) \ - if (creature_instance_is_available(thing, inst_id) \ - && creature_instance_has_reset(thing, inst_id)) { \ - return inst_id; \ - } -#define INSTANCE_RET_NEG_IF_AVAIL_ONLY(thing, inst_id) \ - if (creature_instance_is_available(thing, inst_id)) { \ - return -inst_id; \ - } - TbBool creature_would_benefit_from_healing(const struct Thing* thing) { struct CreatureControl* cctrl = creature_control_get_from_thing(thing); @@ -1852,29 +1842,108 @@ CrInstance get_self_spell_casting(const struct Thing *thing) return CrInst_NULL; } -CrInstance get_best_quick_range_instance_to_use(const struct Thing *thing) -{ - INSTANCE_RET_IF_AVAIL(thing, CrInst_FIREBALL); - INSTANCE_RET_IF_AVAIL(thing, CrInst_FIRE_ARROW); - INSTANCE_RET_IF_AVAIL(thing, CrInst_MISSILE); - INSTANCE_RET_IF_AVAIL(thing, CrInst_NAVIGATING_MISSILE); - INSTANCE_RET_IF_AVAIL(thing, CrInst_LIGHTNING); - INSTANCE_RET_IF_AVAIL(thing, CrInst_HAILSTORM); - INSTANCE_RET_IF_AVAIL(thing, CrInst_GRENADE); - INSTANCE_RET_IF_AVAIL(thing, CrInst_POISON_CLOUD); - INSTANCE_RET_NEG_IF_AVAIL_ONLY(thing, CrInst_FIREBALL); - INSTANCE_RET_NEG_IF_AVAIL_ONLY(thing, CrInst_FIRE_ARROW); - INSTANCE_RET_NEG_IF_AVAIL_ONLY(thing, CrInst_MISSILE); - INSTANCE_RET_NEG_IF_AVAIL_ONLY(thing, CrInst_NAVIGATING_MISSILE); - INSTANCE_RET_NEG_IF_AVAIL_ONLY(thing, CrInst_LIGHTNING); - INSTANCE_RET_NEG_IF_AVAIL_ONLY(thing, CrInst_HAILSTORM); - INSTANCE_RET_NEG_IF_AVAIL_ONLY(thing, CrInst_GRENADE); - INSTANCE_RET_NEG_IF_AVAIL_ONLY(thing, CrInst_POISON_CLOUD); - return CrInst_NULL; +// Static array to store the IDs of "postal" instances +static CrInstance postal_inststance[INSTANCE_TYPES_MAX]; +// Counter for the number of "postal" instances found +static short postal_inst_num = 0; +// Flag to indicate if the cache has been initialized +static TbBool initial = false; + +/** @brief Retrieves a random available "postal" instance within range for a given creature. + * + * On the first call, the function creates a cache of all available "postal" instances. + * It then loops through the cache to find instances available for the creature and fitting within the given range. + * These available instances are added to a list. + * The function then chooses a random instance from this list. + * + * @param thing Pointer to the creature for which the instance is to be retrieved. + * @param dist Distance to the target. + * @return A random available "postal" CrInstance for the given range + */ +CrInstance get_postal_instance_to_use(const struct Thing *thing, unsigned long dist) +{ + struct InstanceInfo* inst_inf; + + // Initialize the cache only once + if (!initial) + { + // Loop through all available instances + for (short i = 0; i < game.conf.crtr_conf.instances_count; i++) + { + inst_inf = creature_instance_info_get(i); + // Check if the instance has a positive postal_priority + if (inst_inf->postal_priority > 0) + { + // Ensure we don't exceed the maximum array size + if (postal_inst_num < INSTANCE_TYPES_MAX) + { + // Add the instance ID to the cache + postal_inststance[postal_inst_num++] = i; + } + else { + break; + } + } + } + // Mark the cache as initialized + initial = true; + } + + //List of usable instances + CrInstance av_postal_inst[INSTANCE_TYPES_MAX]; + short av_postal_inst_num = 0; + char highest_prio = 0; + short highest_prio_idx = CrInst_NULL; + // Loop through the cached postal instances + for (short j = 0; j < postal_inst_num; j++) + { + inst_inf = creature_instance_info_get(postal_inststance[j]); + + // Check if the instance is available + if (creature_instance_is_available(thing, postal_inststance[j])) + { + // If this instance has higher priority than current highest, reset the list + if (inst_inf->postal_priority > highest_prio) + { + highest_prio = inst_inf->postal_priority; + av_postal_inst_num = 0; // Clear the list as we found a higher priority + } + + // If this instance matches the highest priority, check further conditions + if (inst_inf->postal_priority == highest_prio) + { + // Check if the instance is reset and in range + if (creature_instance_has_reset(thing, postal_inststance[j]) && + inst_inf->range_min <= dist && dist <= inst_inf->range_max) + { + // Add to the list of available instances + av_postal_inst[av_postal_inst_num++] = postal_inststance[j]; + } + } + } + } + + // Choose a random index from the list of usable instances + if (av_postal_inst_num > 0) + { + short rand_inst_idx = CREATURE_RANDOM(thing, av_postal_inst_num); + return av_postal_inst[rand_inst_idx]; + } + else + { + // Return NULL if no suitable instance is found + return CrInst_NULL; + } +} + +void reset_postal_instance_cache() +{ + // Reset the cache variables + postal_inst_num = 0; + initial = false; + memset(postal_inststance, 0, sizeof(postal_inststance)); } -#undef INSTANCE_RET_IF_AVAIL -#undef INSTANCE_RET_NEG_IF_AVAIL_ONLY /** * Gives combat weapon instance from given array which matches given distance. diff --git a/src/creature_states_combt.h b/src/creature_states_combt.h index e8dcb80b4d..6d0e9f4660 100644 --- a/src/creature_states_combt.h +++ b/src/creature_states_combt.h @@ -73,7 +73,8 @@ TbBool creature_would_benefit_from_healing(const struct Thing* thing); long project_creature_attack_target_damage(const struct Thing *firing, const struct Thing *target); -CrInstance get_best_quick_range_instance_to_use(const struct Thing *thing); +void reset_postal_instance_cache(); +CrInstance get_postal_instance_to_use(const struct Thing *thing, unsigned long dist); TbBool creature_will_do_combat(const struct Thing *thing); TbBool creature_look_for_combat(struct Thing *creatng); diff --git a/src/creature_states_mood.c b/src/creature_states_mood.c index 5db338fa36..c5e51b9c51 100644 --- a/src/creature_states_mood.c +++ b/src/creature_states_mood.c @@ -507,17 +507,20 @@ TbBool find_combat_target_passing_by_room_but_having_unrelated_job(const struct TbBool process_job_causes_going_postal(struct Thing *creatng, struct Room *room, CreatureJob going_postal_job) { struct CreatureStats* crstat = creature_stats_get_from_thing(creatng); - CrInstance inst_use = get_best_quick_range_instance_to_use(creatng); - if (inst_use <= 0) { - SYNCDBG(8,"The %s index %d cannot go postal during %s; no ranged instance",thing_model_name(creatng),(int)creatng->index,creature_job_code_name(going_postal_job)); - return false; - } // Find a target unsigned long combt_dist = LONG_MAX; struct Thing* combt_thing = INVALID_THING; if (find_combat_target_passing_by_room_but_having_unrelated_job(creatng, going_postal_job, room, &combt_dist, &combt_thing)) { SYNCDBG(8,"The %s index %d goes postal on %s index %d during %s",thing_model_name(creatng),(int)creatng->index,thing_model_name(combt_thing),(int)combt_thing->index,creature_job_code_name(going_postal_job)); + + CrInstance inst_use = get_postal_instance_to_use(creatng, combt_dist); + if (inst_use <= 0) + { + SYNCDBG(8,"The %s index %d cannot go postal during %s; no ranged instance",thing_model_name(creatng),(int)creatng->index,creature_job_code_name(going_postal_job)); + return false; + } + set_creature_instance(creatng, inst_use, combt_thing->index, 0); external_set_thing_state(combt_thing, CrSt_CreatureEvacuateRoom); struct CreatureControl* combctrl = creature_control_get_from_thing(combt_thing); @@ -634,8 +637,7 @@ TbBool any_worker_will_go_postal_on_creature_in_room(const struct Room *room, co { if (creature_will_go_postal_on_victim_during_job(thing, victng, going_postal_job)) { - // We need quick ranged instance to go postal - if (creature_has_quick_range_weapon(thing)) { + if (creature_has_weapon_for_postal(thing)) { return true; } } diff --git a/src/main.cpp b/src/main.cpp index 6e5fea5c9c..47f037972c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -101,6 +101,7 @@ #include "creature_states.h" #include "creature_instances.h" #include "creature_graphics.h" +#include "creature_states_combt.h" #include "creature_states_mood.h" #include "lens_api.h" #include "light_data.h" @@ -1578,7 +1579,7 @@ void reinit_level_after_load(void) sound_reinit_after_load(); music_reinit_after_load(); update_panel_colors(); - + reset_postal_instance_cache(); } /** diff --git a/src/main_game.c b/src/main_game.c index e8d1f5d95d..94d0057da8 100644 --- a/src/main_game.c +++ b/src/main_game.c @@ -22,6 +22,7 @@ #include "config_compp.h" #include "config_settings.h" +#include "creature_states_combt.h" #include "dungeon_data.h" #include "engine_lenses.h" #include "engine_redraw.h" @@ -236,6 +237,7 @@ static void init_level(void) game.manufactr_element = 0; game.manufactr_spridx = 0; game.manufactr_tooltip = 0; + reset_postal_instance_cache(); JUSTMSG("Started level %d from %s", get_selected_level_number(), campaign.name); api_event("GAME_STARTED");