diff --git a/config/fxdata/creature.cfg b/config/fxdata/creature.cfg index b4ed5f4c39..951b29d280 100644 --- a/config/fxdata/creature.cfg +++ b/config/fxdata/creature.cfg @@ -58,9 +58,9 @@ TooltipTextID = 201 SymbolSprites = 0 ; Graphics animation used to display creature during the instance Graphics = STAND -; Minimum distance the enemy can be to cast the offensive spell. +; Minimum distance the target can be to cast the spell. RangeMin = 0 -; Maximal distance the enemy can be to cast the offensive spell. +; Maximal distance the target can be to cast the spell. RangeMax = 0 ; Type of thing the aim assist will target first. Full meaning unknown, range 1~8. ; 8 targets anything, creatures targetted by all. @@ -69,10 +69,11 @@ RangeMax = 0 ; 7 will target traps. PrimaryTarget = 0 ; Instance property flags: -; RANGED_DEBUFF will be used in battle, but does not count as an attack +; RANGED_DEBUFF will be used on enemy creatures, but does not count as an attack ; DANGEROUS will be avoided when attacking Doors and Hearts ; DESTRUCTIVE is required when attacking Doors and Hearts -; SELF_BUFF does not do anything +; SELF_BUFF can be applied to caster. +; RANGED_BUFF can be applied to another friendly creature. ; REPEAT_TRIGGER allows player to hold down the mouse button to cast ; QUICK ranged attacks will be used by units going postal ; DISARMING allows instance to be used against traps @@ -80,6 +81,10 @@ PrimaryTarget = 0 Properties = ; Function used as the instance action, and its parameters Function = none 0 0 +; Functions used validate the source and the target of the spell. +ValidateFunc = validate_source_generic validate_target_generic +; Functions used search targets of the spell. +SearchTargetsFunc = search_target_generic [instance1] Name = SWING_WEAPON_SWORD @@ -946,6 +951,27 @@ Properties = SELF_BUFF PrimaryTarget = 8 Function = creature_cast_spell SPELL_SUMMON_CREATURE 0 +[instance50] +Name = RANGED_HEAL +Time = 10 +ActionTime = 6 +ResetTime = 400 +FPTime = 5 +FPActionTime = 3 +FPResetTime = 250 +FPInstantCast = 0 +ForceVisibility = 10 +TooltipTextID = 1066 +SymbolSprites = 414 +Graphics = ATTACK +RangeMin = MIN +RangeMax = 5120 +PrimaryTarget = 6 +Properties = RANGED_BUFF SELF_BUFF +Function = creature_cast_spell SPELL_HEAL 0 +ValidateFunc = validate_source_ranged_heal validate_target_ranged_heal +SearchTargetsFunc = search_target_ranged_heal + [job0] ; Empty job, indicates no job assigned Name = NULL diff --git a/config/fxdata/crstates.cfg b/config/fxdata/crstates.cfg index d8085c7014..67dc33fd54 100644 --- a/config/fxdata/crstates.cfg +++ b/config/fxdata/crstates.cfg @@ -23,7 +23,7 @@ Name = ImpDigsDirt Name = ImpMinesGold [state6] -Name = Null6 +Name = CreatureCastingPreparation [state7] Name = ImpDropsGold diff --git a/config/fxdata/effects.toml b/config/fxdata/effects.toml index c5f35a79fd..0551502120 100644 --- a/config/fxdata/effects.toml +++ b/config/fxdata/effects.toml @@ -5017,3 +5017,43 @@ LightRadius = 0 LightIntensity = 0 LightFlags = 0 AffectedByWind = 1 + +[effectElement119] +Name = "EFFECTELEMENT_HEAL_BEAM" +DrawClass = 2 +MoveType = 1 +Unanimated = 0 +Lifespan = [0,2] +AnimationId = 802 +SpriteSize = [122,142] +RenderFlags = 0 +SpriteSpeed = [192,256] +AnimateOnFloor = false +Unshaded = true +Transparent = 3 +MovementFlags = 0 +SizeChange = 0 +FallAcceleration = 10 +InertiaFloor = 102 +InertiaAir = 0 +SubeffectModel = 0 +SubeffectDelay = 0 +Movable = true +Impacts = true +SolidGroundEffmodel = 0 +SolidGroundSoundId = 0 +SolidGroundLoudness = 256 +SolidGroundDestroyOnImpact = false +WaterEffmodel = 19 +WaterSoundId = 36 +WaterLoudness = 80 +WaterDestroyOnImpact = false +LavaEffmodel = 0 +LavaSoundId = 0 +LavaLoudness = 256 +LavaDestroyOnImpact = false +TransformModel = 0 +LightRadius = 0 +LightIntensity = 0 +LightFlags = 0 +AffectedByWind = 1 \ No newline at end of file diff --git a/config/fxdata/magic.cfg b/config/fxdata/magic.cfg index 55ef296d0a..b3cf67654f 100644 --- a/config/fxdata/magic.cfg +++ b/config/fxdata/magic.cfg @@ -88,8 +88,8 @@ SymbolSprites = 355 412 [spell7] Name = SPELL_HEAL -CastAtThing = 0 -ShotModel = NOSHOT +CastAtThing = 1 +ShotModel = SHOT_RANGED_HEAL SpellPower = POWER_HEAL_CREATURE Duration = 50 AuraEffect = -45 @@ -1329,6 +1329,28 @@ WithstandHitAgainst = WALL DOOR Properties = NO_HIT REBOUND_IMMUNE WIND_IMMUNE EXPLODE_FLESH NO_STUN UpdateLogic = 7 +[shot33] +Name = SHOT_RANGED_HEAL +Animation = 0 +AnimationSize = 0 +AnimationTransparency = 0 +Size_XY = 128 +Size_Z = 128 +Health = 2 +Damage = 0 +DamageType = +HitType = 11 +Speed = 256 +MaxRange = 5120 +BaseExperienceGain = 0 +DestroyOnHit = 0 +TargetHitstopTurns = 0 +SpellEffect = 7 +Properties = REBOUND_IMMUNE WIND_IMMUNE HIDDEN_PROJECTILE +FireLogic = 1 +EffectModel = EFFECTELEMENT_HEAL_BEAM +EffectSpacing = 96 + ; Powers types. [power0] diff --git a/lang/gtext_chi.po b/lang/gtext_chi.po index b7fde005a9..894c81cbcf 100644 --- a/lang/gtext_chi.po +++ b/lang/gtext_chi.po @@ -1658,7 +1658,7 @@ msgstr "冰雹术: 发射密集的冰雹块。" #: guitext:245 msgctxt "Creature spell" -msgid "Slow: Slows down the target creature, making it attacks and movement delayed." +msgid "Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "缓慢术: 降低目标怪物的速度,使它的行动和攻击变得迟缓。" #: guitext:246 diff --git a/lang/gtext_cht.po b/lang/gtext_cht.po index 79e0e8d6e1..30761501a5 100644 --- a/lang/gtext_cht.po +++ b/lang/gtext_cht.po @@ -2105,7 +2105,7 @@ msgstr "冰雹术: [26.10]" #, fuzzy msgctxt "Creature spell" msgid "" -"Slow: Slows down the target creature, making it attacks and movement delayed." +"Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "缓慢术: [26.19]" #: guitext:246 diff --git a/lang/gtext_cze.po b/lang/gtext_cze.po index 868ffabbc3..3fc7142ad5 100644 --- a/lang/gtext_cze.po +++ b/lang/gtext_cze.po @@ -2240,7 +2240,7 @@ msgstr "Krupobiti:" #: guitext:245 msgctxt "Creature spell" msgid "" -"Slow: Slows down the target creature, making it attacks and movement delayed." +"Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "Zpomaleni:" #: guitext:246 diff --git a/lang/gtext_dut.po b/lang/gtext_dut.po index b589bad172..92bd7047a8 100644 --- a/lang/gtext_dut.po +++ b/lang/gtext_dut.po @@ -2249,7 +2249,7 @@ msgstr "Hagelstorm: [26.10]" #, fuzzy msgctxt "Creature spell" msgid "" -"Slow: Slows down the target creature, making it attacks and movement delayed." +"Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "Vertragen: [26.19]" #: guitext:246 diff --git a/lang/gtext_eng.pot b/lang/gtext_eng.pot index cd5f0bc05d..e031cbf005 100644 --- a/lang/gtext_eng.pot +++ b/lang/gtext_eng.pot @@ -1503,7 +1503,7 @@ msgstr "" #: guitext:245 msgctxt "Creature spell" -msgid "Slow: Slows down the target creature, making it attacks and movement delayed." +msgid "Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "" #: guitext:246 @@ -5388,117 +5388,117 @@ msgid "Moo4" msgstr "" #: guitext:1011 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Dungeon Keeper - Original Campaign" msgstr "" #: guitext:1012 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Assmist Isle" msgstr "" #: guitext:1013 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Ancient Keeper campaign" msgstr "" #: guitext:1014 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Burdened Imps' Level Pack" msgstr "" #: guitext:1015 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Conquest of the Arctic" msgstr "" #: guitext:1016 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "The Destiny of Ninja" msgstr "" #: guitext:1017 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "DzjeeAr's 6-level campaign" msgstr "" #: guitext:1018 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "DzjeeAr's 10-level campaign" msgstr "" #: guitext:1019 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "DzjeeAr's 25-level campaign" msgstr "" #: guitext:1020 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Evil Keeper campaign" msgstr "" #: guitext:1021 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Grinics' KReign campaign" msgstr "" #: guitext:1022 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Japanese DKMaps8 pack" msgstr "" #: guitext:1023 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "KDK Levels" msgstr "" #: guitext:1024 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Good Campaign" msgstr "" #: guitext:1025 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Lord Vexer campaign" msgstr "" #: guitext:1026 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Nikolai's Castles campaign" msgstr "" #: guitext:1027 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Dungeon Keeper - NG+" msgstr "" #: guitext:1028 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Post Ancient Keeper campaign" msgstr "" #: guitext:1029 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Post Undead Keeper campaign" msgstr "" #: guitext:1030 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Quest for the Hero campaign" msgstr "" #: guitext:1031 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Revenge of the Lord" msgstr "" #: guitext:1032 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Twin Keepers Campaign" msgstr "" #: guitext:1033 -msgctxt "Menu interface item\"" +msgctxt "Menu interface item" msgid "Undead Keeper campaign" msgstr "" diff --git a/lang/gtext_fre.po b/lang/gtext_fre.po index 9888527ac8..79c9009f0c 100644 --- a/lang/gtext_fre.po +++ b/lang/gtext_fre.po @@ -2284,7 +2284,7 @@ msgstr "Grêle : Crée une pluie de grêlons partout où le sort s'abat.[26.10]" #: guitext:245 msgctxt "Creature spell" msgid "" -"Slow: Slows down the target creature, making it attacks and movement delayed." +"Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "" "Lenteur : Ralentit la créature cible, ce qui ralentit ses attaques et " "ses mouvements." diff --git a/lang/gtext_ger.po b/lang/gtext_ger.po index 397aaddf2d..0d89988389 100644 --- a/lang/gtext_ger.po +++ b/lang/gtext_ger.po @@ -2308,7 +2308,7 @@ msgstr "Hagelsturm: Entfesselt einen Sturm aus Hagelkörnern. [26.10]" #: guitext:245 msgctxt "Creature spell" msgid "" -"Slow: Slows down the target creature, making it attacks and movement delayed." +"Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "Verlangsamen: Bremst die Angriffe und Bewegungen der Zielkreatur erheblich. [26.19]" #: guitext:246 diff --git a/lang/gtext_ita.po b/lang/gtext_ita.po index 547f1e1205..5a3935eba3 100644 --- a/lang/gtext_ita.po +++ b/lang/gtext_ita.po @@ -2275,7 +2275,7 @@ msgstr "Tempesta di Grandine: [26.10]" #, fuzzy msgctxt "Creature spell" msgid "" -"Slow: Slows down the target creature, making it attacks and movement delayed." +"Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "Rallentamento: [26.19]" #: guitext:246 diff --git a/lang/gtext_kor.po b/lang/gtext_kor.po index 06c5495a09..2f693b7fd4 100644 --- a/lang/gtext_kor.po +++ b/lang/gtext_kor.po @@ -2223,7 +2223,7 @@ msgstr "우박 폭풍: 우박이 맹위를 떨칩니다." #: guitext:245 msgctxt "Creature spell" msgid "" -"Slow: Slows down the target creature, making it attacks and movement delayed." +"Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "느려림: 대상 피조물의 속도가 느려져 공격 및 이동이 지연됩니다." #: guitext:246 diff --git a/lang/gtext_lat.po b/lang/gtext_lat.po index 681be04dfe..82ec6f7245 100644 --- a/lang/gtext_lat.po +++ b/lang/gtext_lat.po @@ -1650,7 +1650,7 @@ msgstr "Tempestas Grandinis: Pluviam grandinis creat ubicumque incantatio iacitu #: guitext:245 msgctxt "Creature spell" -msgid "Slow: Slows down the target creature, making it attacks and movement delayed." +msgid "Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "Lentitudo: Altera creatura lenta fit, quod efficit se aggredi et movere lentius." #: guitext:246 diff --git a/lang/gtext_pol.po b/lang/gtext_pol.po index 55d1a7a022..0a1dcabf04 100644 --- a/lang/gtext_pol.po +++ b/lang/gtext_pol.po @@ -2309,7 +2309,7 @@ msgstr "Burza gradowa: Tworzy opad wielkiego gradu w miejscu rzucenia czaru." #: guitext:245 msgctxt "Creature spell" msgid "" -"Slow: Slows down the target creature, making it attacks and movement delayed." +"Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "Spowolnienie: Spowalnia stwora, opóźniając jego ruch i ataki." #: guitext:246 diff --git a/lang/gtext_rus.po b/lang/gtext_rus.po index 72dc717d22..42651829f3 100644 --- a/lang/gtext_rus.po +++ b/lang/gtext_rus.po @@ -2329,7 +2329,7 @@ msgstr "Сильный град: Вызывает бурю с градом." #: guitext:245 msgctxt "Creature spell" msgid "" -"Slow: Slows down the target creature, making it attacks and movement delayed." +"Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "" "Замедление: Замедляет создание, что снижает скорость его атак и перемещения." diff --git a/lang/gtext_spa.po b/lang/gtext_spa.po index c0f8beb280..792f8baab4 100644 --- a/lang/gtext_spa.po +++ b/lang/gtext_spa.po @@ -1794,7 +1794,7 @@ msgstr "Tormenta de granizo: Crea una lluvia de granizo allá donde el hechizo i #: guitext:245 msgctxt "Creature spell" -msgid "Slow: Slows down the target creature, making it attacks and movement delayed." +msgid "Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "Lentitud: Ralentiza la criatura objetivo, haciendo que su ataque y movimiento sean más lentos." #: guitext:246 diff --git a/lang/gtext_swe.po b/lang/gtext_swe.po index 7b51aa5214..ed87300da2 100644 --- a/lang/gtext_swe.po +++ b/lang/gtext_swe.po @@ -1860,7 +1860,7 @@ msgstr "Hagelstorm: Frammanar en skur av hagel." #: guitext:245 msgctxt "Creature spell" -msgid "Slow: Slows down the target creature, making it attacks and movement delayed." +msgid "Slow: Slows down the target creature, making its attacks and movement delayed." msgstr "Långsam: Gör fienden långsam så att dess attacker och rörelse blir fördröjda. " #: guitext:246 diff --git a/src/config_creature.c b/src/config_creature.c index 246e27b81a..8ac4dad744 100644 --- a/src/config_creature.c +++ b/src/config_creature.c @@ -94,6 +94,8 @@ const struct NamedCommand creaturetype_instance_commands[] = { {"PROPERTIES", 15}, {"FPINSTANTCAST", 16}, {"PRIMARYTARGET", 17}, + {"VALIDATEFUNC", 18}, + {"SEARCHTARGETSFUNC", 19}, {NULL, 0}, }; @@ -108,6 +110,7 @@ const struct NamedCommand creaturetype_instance_properties[] = { {"QUICK", InstPF_Quick}, {"DISARMING", InstPF_Disarming}, {"DISPLAY_SWIPE", InstPF_UsesSwipe}, + {"RANGED_BUFF", InstPF_RangedBuff}, {NULL, 0}, }; @@ -833,7 +836,9 @@ TbBool parse_creaturetype_instance_blocks(char *buf, long len, const char *confi game.conf.magic_conf.instance_info[i].tooltip_stridx = 0; game.conf.magic_conf.instance_info[i].range_min = -1; game.conf.magic_conf.instance_info[i].range_max = -1; - + game.conf.magic_conf.instance_info[i].validate_func_idx[0] = 1; + game.conf.magic_conf.instance_info[i].validate_func_idx[1] = 2; + game.conf.magic_conf.instance_info[i].search_func_idx = 1; } } } @@ -1112,7 +1117,7 @@ TbBool parse_creaturetype_instance_blocks(char *buf, long len, const char *confi if (k > 0) { inst_inf->instance_property_flags |= k; - n++; + n++; } else { CONFWRNLOG("Incorrect value of \"%s\" parameter \"%s\" in [%s] block of %s file.", COMMAND_TEXT(cmd_num),word_buf,block_buf,config_textname); @@ -1146,6 +1151,28 @@ TbBool parse_creaturetype_instance_blocks(char *buf, long len, const char *confi COMMAND_TEXT(cmd_num), block_buf, config_textname); } break; + case 18: // VALIDATE_FUNC + k = recognize_conf_parameter(buf, &pos, len, creature_instances_validate_func_type); + if (k > 0) + { + inst_inf->validate_func_idx[0] = k; + n++; + } + k = recognize_conf_parameter(buf, &pos, len, creature_instances_validate_func_type); + if (k > 0) + { + inst_inf->validate_func_idx[1] = k; + n++; + } + break; + case 19: // SEARCH_TARGETS_FUNC + k = recognize_conf_parameter(buf, &pos, len, creature_instances_search_targets_func_type); + if (k > 0) + { + inst_inf->search_func_idx = k; + n++; + } + break; case 0: // comment break; case -1: // end of buffer diff --git a/src/config_creature.h b/src/config_creature.h index 9aa60756d3..e9f235232d 100644 --- a/src/config_creature.h +++ b/src/config_creature.h @@ -148,6 +148,7 @@ enum InstancePropertiesFlags { InstPF_Quick = 0x0080, InstPF_Disarming = 0x0100, InstPF_UsesSwipe = 0x0200, + InstPF_RangedBuff = 0x0400, }; enum CreatureDeathKind { diff --git a/src/config_magic.h b/src/config_magic.h index 2fe980893b..9c6a922ae1 100644 --- a/src/config_magic.h +++ b/src/config_magic.h @@ -66,6 +66,8 @@ enum SpellKinds { SplK_Chicken, SplK_TimeBomb,//[28] SplK_Lizard, + Splk_SummonFamiliar, + Splk_SummonCreature, }; enum CreatureSpellAffectedFlags { diff --git a/src/creature_instances.c b/src/creature_instances.c index cd3b75053d..65b4d4fd12 100644 --- a/src/creature_instances.c +++ b/src/creature_instances.c @@ -35,6 +35,7 @@ #include "creature_states.h" #include "creature_states_combt.h" #include "config_creature.h" +#include "config_crtrstates.h" #include "config_effects.h" #include "power_specials.h" #include "room_data.h" @@ -58,6 +59,8 @@ extern "C" { #endif +#define MAX_CREATURES_SEARCHED 100 + /******************************************************************************/ long instf_attack_room_slab(struct Thing *creatng, long *param); long instf_creature_cast_spell(struct Thing *creatng, long *param); @@ -110,6 +113,35 @@ Creature_Instf_Func creature_instances_func_list[] = { NULL, }; +const struct NamedCommand creature_instances_validate_func_type[] = { + {"validate_source_generic", 1}, + {"validate_target_generic", 2}, + {"validate_source_ranged_heal", 3}, + {"validate_target_ranged_heal", 4}, + {NULL, 0}, +}; + +Creature_Validate_Func creature_instances_validate_func_list[] = { + NULL, + validate_source_generic, + validate_target_generic, + validate_source_ranged_heal, + validate_target_ranged_heal, + NULL, +}; + +const struct NamedCommand creature_instances_search_targets_func_type[] = { + {"search_target_generic", 1}, + {"search_target_ranged_heal", 2}, + {NULL, 0}, +}; + +Creature_Target_Search_Func creature_instances_search_targets_func_list[] = { + NULL, + search_target_generic, + search_target_ranged_heal, + NULL, +}; /******************************************************************************/ #ifdef __cplusplus } @@ -178,7 +210,7 @@ void creature_increase_available_instances(struct Thing *thing) } else if ( (crstat->learned_instance_level[i] > cctrl->explevel+1) && !(game.conf.rules.game.classic_bugs_flags & ClscBug_RebirthKeepsSpells) ) { - cctrl->instance_available[k] = false; + cctrl->instance_available[k] = false; } } } @@ -424,6 +456,7 @@ void process_creature_instance(struct Thing *thing) cctrl->inst_repeat = 0; return; } + SYNCDBG(18,"Finalize %s for %s index %d.",creature_instance_code_name(cctrl->instance_id),thing_model_name(thing),(int)thing->index); cctrl->instance_id = CrInst_NULL; thing->creature.volley_fire = false; } @@ -492,19 +525,28 @@ long instf_creature_cast_spell(struct Thing *creatng, long *param) struct CreatureControl* cctrl = creature_control_get_from_thing(creatng); long spl_idx = param[0]; struct SpellConfig* spconf = get_spell_config(spl_idx); - SYNCDBG(8,"The %s index %d casts %s",thing_model_name(creatng),(int)creatng->index,spell_code_name(spl_idx)); - if (spconf->cast_at_thing) + struct Thing* target = NULL; + + SYNCDBG(8,"The %s(%d) casts %s at %d", thing_model_name(creatng), (int)creatng->index, + spell_code_name(spl_idx), cctrl->targtng_idx); + + if (spconf->cast_at_thing && cctrl->targtng_idx != creatng->index) { - struct Thing* trthing = thing_get(cctrl->targtng_idx); - if (!thing_is_invalid(trthing)) - { - creature_cast_spell_at_thing(creatng, trthing, spl_idx, cctrl->explevel); - // Start cooldown after spell is cast - cctrl->instance_use_turn[cctrl->instance_id] = game.play_gameturn; - return 0; - } + // If the targtng_idx is just the caster itself, we can call creature_cast_spell + // instead of creature_cast_spell_at_thing. + target = thing_get(cctrl->targtng_idx); + if (thing_is_invalid(target)) target = NULL; + } + + if (target != NULL) + { + creature_cast_spell_at_thing(creatng, target, spl_idx, cctrl->explevel); + } + else + { + creature_cast_spell(creatng, spl_idx, cctrl->explevel, cctrl->targtstl_x, cctrl->targtstl_y); } - creature_cast_spell(creatng, spl_idx, cctrl->explevel, cctrl->targtstl_x, cctrl->targtstl_y); + // Start cooldown after spell effect activates cctrl->instance_use_turn[cctrl->instance_id] = game.play_gameturn; return 0; @@ -535,6 +577,66 @@ long process_creature_self_spell_casting(struct Thing* creatng) return 1; } +/** + * @brief Check whether the given creature is suitable to cast ranged buff spell. + * This function is used for both combat and non-combat situations. + * + * @param creatng The creature being checked. + * @return CrInstance The instance index being set. + */ +CrInstance process_creature_ranged_buff_spell_casting(struct Thing* creatng) +{ + TRACE_THING(creatng); + SYNCDBG(8,"Processing %s(%d), act.st: %s, con.st: %s", thing_model_name(creatng), creatng->index, + creature_state_code_name(creatng->active_state), creature_state_code_name(creatng->continue_state)); + + CrInstance i = CrInst_NULL; + for(; i < game.conf.crtr_conf.instances_count; i++ ) + { + const struct InstanceInfo* inst_inf = creature_instance_info_get(i); + if((inst_inf->instance_property_flags & InstPF_RangedBuff) == 0) + { + continue; + } + if((inst_inf->validate_func_idx[0] == 0) || (inst_inf->validate_func_idx[1] == 0) || + (inst_inf->search_func_idx == 0)) + { + ERRORLOG("The instance %d has no validate function or search function.", i); + continue; + } + if(!creature_instances_validate_func_list[inst_inf->validate_func_idx[0]](creatng, NULL, i)) + { + // The input creature is not a legal source. + continue; + } + + struct Thing* targets = NULL; + short found_count = 0; + if(creature_instances_search_targets_func_list[inst_inf->search_func_idx](creatng, i, &targets, &found_count) && + targets && (found_count > 0)) + { + SYNCDBG(8, "Set instance %s on %s(%d) for %s(%d).", + creature_instance_code_name(i), thing_model_name(creatng), creatng->index, + thing_model_name(targets), targets->index); + + struct CreatureControl* cctrl = creature_control_get_from_thing(creatng); + // Enter the temporary state. + cctrl->active_state_bkp = creatng->active_state; + cctrl->continue_state_bkp = creatng->continue_state; + internal_set_thing_state(creatng, CrSt_CreatureCastingPreparation); + + // Apply the spell instance to the first one since we have no group buff yet. + set_creature_instance(creatng, i, targets->index, NULL); + free(targets); + break; // No need to check the next spell instance. + + } + free(targets); + } + + return (i < game.conf.crtr_conf.instances_count) ? i : CrInst_NULL; +} + long instf_dig(struct Thing *creatng, long *param) { long stl_x; @@ -836,7 +938,7 @@ long instf_first_person_do_imp_task(struct Thing *creatng, long *param) if (room->owner == creatng->owner) { TbBool slab_diggable = subtile_is_diggable_for_player(creatng->owner, slab_subtile_center(ahead_slb_x), slab_subtile_center(ahead_slb_y), true); - if (!slab_diggable) + if (!slab_diggable) { if (creatng->creature.gold_carried > 0) { @@ -1051,4 +1153,251 @@ void delay_heal_sleep(struct Thing *creatng) struct CreatureControl* cctrl = creature_control_get_from_thing(creatng); cctrl->healing_sleep_check_turn = game.play_gameturn + 600; } + +/** + * @brief Check if the given creature can cast the specified spell by examining general conditions. + * + * @param source The source creature + * @param target The target creature + * @param inst_idx The spell instance index + * @return TbBool True if the creature can, false if otherwise. + */ +TbBool validate_source_generic(struct Thing *source, struct Thing *target, CrInstance inst_idx) +{ + if ((source->alloc_flags & TAlF_IsControlled) != 0) + { + // If this creature is under player's control (Possession). + return false; + } + // We assume we usually don't want to overwrite the original instance. + struct CreatureControl* cctrl = creature_control_get_from_thing(source); + if (cctrl->instance_id != CrInst_NULL) { + SYNCDBG(11, "This creature already has an instance %s.", creature_instance_code_name(cctrl->instance_id)); + return false; + } + + if (!creature_instance_is_available(source, inst_idx) || + !creature_instance_has_reset(source, inst_idx) || + creature_is_fleeing_combat(source) || creature_affected_by_spell(source, SplK_Chicken) || + creature_is_being_unconscious(source) || creature_is_dying(source) || + thing_is_picked_up(source) || creature_is_being_dropped(source) || + creature_is_being_sacrificed(source) || creature_is_being_summoned(source)) + { + return false; + } + + return true; +} + +/** + * @brief Check if the given creature can be the target of the specified spell by examining general conditions. + * @param source The source creature + * @param target The target creature + * @param inst_idx The spell instance index + * @return TbBool True if the creature can be, false if otherwise. + */ +TbBool validate_target_generic(struct Thing *source, struct Thing *target, CrInstance inst_idx) +{ + // Note that we don't check "unconscious". + // We don't check the spatial conditions, such as distacne, angle, and sight here, because + // they should be checked in the search function. + if (creature_is_dying(target) || thing_is_picked_up(target) || creature_is_being_dropped(target) || + creature_is_being_sacrificed(target) || creature_is_being_summoned(target)) + { + return false; + } + + struct InstanceInfo* inst_inf = creature_instance_info_get(inst_idx); + if ((inst_inf->instance_property_flags & InstPF_SelfBuff) == 0 && source->index == target->index) + { + // If this spell doesn't have SELF_BUFF flag, exclude itself. + return false; + } + + return true; +} + +/** + * @brief Check if the given creature can cast Ranged Heal spell. + * + * @param source The source creature + * @param target The target creature + * @param inst_idx The spell instance index + * @return TbBool True if the creature can, false if otherwise. + */ +TbBool validate_source_ranged_heal(struct Thing *source, struct Thing *target, CrInstance inst_idx) +{ + if (!validate_source_generic(source, target, CrInst_RANGED_HEAL)) + { + return false; + } + // Creature who is leaving doesn't care about any allies. + if (source->continue_state == CrSt_CreatureLeaves || + source->active_state == CrSt_CreatureLeavingDungeon || + source->active_state == CrSt_CreatureScavengedDisappear ) + { + return false; + } + return true; +} + +/** + * @brief Check if the given creature can be the target of Ranged Heal. + * + * @param source The source creature + * @param target The target creature + * @param inst_idx The spell instance index + * @return TbBool True if the creature can, false if otherwise. + */ +TbBool validate_target_ranged_heal(struct Thing *source, struct Thing *target, CrInstance inst_idx) +{ + if (!validate_target_generic(source, target, CrInst_RANGED_HEAL) || + // Creature who is leaving doesn't deserve heal from allies. + target->continue_state == CrSt_CreatureLeaves || + target->active_state == CrSt_CreatureLeavingDungeon || + target->active_state == CrSt_CreatureScavengedDisappear || + !creature_would_benefit_from_healing(target) || + // Candidate shouldn't be fight with the caster. + creature_has_creature_in_combat(source, target) || + creature_has_creature_in_combat(target, source)) + { + return false; + } + return true; +} + +/** + * @brief Search the suitable targets for given spell. + * + * @param source The source creature + * @param inst_idx The spell instance index + * @param targets The list of the found creatures. Caller must free this. + * @param found_count The number of the found creatures. + * @return TbBool True if there is no error, false if otherwise. + */ +TbBool search_target_generic(struct Thing *source, CrInstance inst_idx, struct Thing **targets, short *found_count) +{ + if (!targets || !found_count) + { + ERRORLOG("Invalid parameters!"); + return false; + } + + TbBool ok = true; + // To improve performance, use a smaller number than CREATURES_COUNT. + *targets = malloc(MAX_CREATURES_SEARCHED); + *found_count = 0; + // Note that we only support buff right now, so we only search source's owner's creature. + // For offensive debuff, we need another loop to iterate all enemies. + struct Dungeon* dungeon = get_players_num_dungeon(source->owner); + int creature_idx = dungeon->creatr_list_start; + int k = 0; + while (creature_idx != 0 && (*found_count) < MAX_CREATURES_SEARCHED) + { + struct Thing* candidate = thing_get(creature_idx); + struct CreatureControl* cctrl = creature_control_get_from_thing(candidate); + if (creature_control_invalid(cctrl)) + { + ERRORLOG("Invalid creature control"); + ok = false; + break; + } + + creature_idx = cctrl->players_next_creature_idx; + + const struct InstanceInfo* inst_inf = creature_instance_info_get(inst_idx); + if (inst_inf->validate_func_idx[1] > 0) + { + if(!creature_instances_validate_func_list[inst_inf->validate_func_idx[1]](source, candidate, inst_idx)) + { + // This candidate is out. + continue; + } + } + + // @todo Consider checking thing_in_field_of_view() in the future, now it is buggy. + // We assume that the source must see the target before it can cast the spell. + int range = get_combat_distance(source, candidate); + if (range < inst_inf->range_min || range > inst_inf->range_max || + !creature_can_see_combat_path(source, candidate, range)) + { + continue; + } + + targets[*found_count] = candidate; + (*found_count)++; + + k++; + if (k > CREATURES_COUNT) + { + ERRORLOG("Infinite loop detected when sweeping creatures list"); + ok = false; + break; + } + } + + return ok; +} + +/** + * @brief Search the suitable targets for CrInst_RANGED_HEAL + * + * @param source The source creature + * @param inst_idx The spell instance index + * @param targets The list of the found creatures. Caller must free this. + * @param found_count The number of the found creatures. + * @return TbBool True if there is no error, false if otherwise + */ +TbBool search_target_ranged_heal(struct Thing *source, CrInstance inst_idx, struct Thing **targets, short *found_count) +{ + if (!targets || !found_count) + { + ERRORLOG("Invalid parameters!"); + return false; + } + + if (!search_target_generic(source, inst_idx, targets, found_count)) + { + return false; + } + + struct Thing *best_choice = NULL; + TbBool ok = true; + int i = 0; + for (; i < *found_count; i++) + { + struct Thing *candidate = targets[i]; + if (thing_is_invalid(candidate)) + { + continue; + } + struct CreatureControl* cctrl = creature_control_get_from_thing(candidate); + if (creature_control_invalid(cctrl)) + { + continue; + } + // Note that we only allow one target. No group buff are allowed yet. + // So, we only pick the weakest one creature here. + HitPoints hp_p_best = 0; + if (best_choice) + { + struct CreatureControl* cctrl_best = creature_control_get_from_thing(best_choice); + hp_p_best = 256L * best_choice->health / cctrl_best->max_health; + } + HitPoints hp_p_candiate = 256L * candidate->health / cctrl->max_health; + if (!best_choice || hp_p_candiate < hp_p_best) + { + best_choice = candidate; + } + } + + if (best_choice) + { + targets[0] = best_choice; + *found_count = 1; + } + + return ok; +} + /******************************************************************************/ diff --git a/src/creature_instances.h b/src/creature_instances.h index 05bd87325b..e5b3dc00c4 100644 --- a/src/creature_instances.h +++ b/src/creature_instances.h @@ -71,7 +71,6 @@ enum CreatureInstances { CrInst_DAMAGE_WALL, CrInst_FIRST_PERSON_DIG, CrInst_LIZARD, // 40 - //CrInst_CAST_SPELL_GROUP, CrInst_CAST_SPELL_DISEASE, // 41 CrInst_CAST_SPELL_CHICKEN, CrInst_CAST_SPELL_TIME_BOMB, @@ -81,6 +80,7 @@ enum CreatureInstances { CrInst_RELAXING, CrInst_FAMILIAR, CrInst_SUMMON, + CrInst_RANGED_HEAL, // 50 CrInst_LISTEND, }; @@ -90,6 +90,8 @@ enum CreatureInstances { struct Thing; typedef long (*Creature_Instf_Func)(struct Thing *, long *); +typedef TbBool (*Creature_Validate_Func)(struct Thing *, struct Thing *, CrInstance); +typedef TbBool (*Creature_Target_Search_Func)(struct Thing *, CrInstance, struct Thing **, short *); struct InstanceInfo { TbBool instant; @@ -109,6 +111,10 @@ struct InstanceInfo { long range_max; long symbol_spridx; short tooltip_stridx; + // [0] for source, [1] for target. Refer to creature_instances_validate_func_list + unsigned char validate_func_idx[2]; + // Refer to creature_instances_search_targets_func_list + unsigned char search_func_idx; }; /******************************************************************************/ @@ -117,12 +123,17 @@ struct InstanceInfo { /******************************************************************************/ extern const struct NamedCommand creature_instances_func_type[]; extern Creature_Instf_Func creature_instances_func_list[]; +extern const struct NamedCommand creature_instances_validate_func_type[]; +extern Creature_Validate_Func creature_instances_validate_func_list[]; +extern const struct NamedCommand creature_instances_search_targets_func_type[]; +extern Creature_Target_Search_Func creature_instances_search_targets_func_list[]; /******************************************************************************/ /** Returns creature instance info structure for given instance index. */ #define creature_instance_info_get(inst_idx) creature_instance_info_get_f(inst_idx,__func__) struct InstanceInfo *creature_instance_info_get_f(CrInstance inst_idx,const char *func_name); void process_creature_instance(struct Thing *thing); long process_creature_self_spell_casting(struct Thing* thing); +CrInstance process_creature_ranged_buff_spell_casting(struct Thing* thing); TbBool creature_instance_info_invalid(const struct InstanceInfo *inst_inf); TbBool creature_instance_is_available(const struct Thing *thing, CrInstance inum); @@ -144,6 +155,14 @@ TbBool instance_draws_possession_swipe(CrInstance inum); void delay_teleport(struct Thing *creatng); void delay_heal_sleep(struct Thing *creatng); /******************************************************************************/ +TbBool validate_source_generic(struct Thing *source, struct Thing *target, CrInstance inst_idx); +TbBool validate_target_generic(struct Thing *source, struct Thing *target, CrInstance inst_idx); +TbBool validate_source_ranged_heal(struct Thing *source, struct Thing *target, CrInstance inst_idx); +TbBool validate_target_ranged_heal(struct Thing *source, struct Thing *target, CrInstance inst_idx); + +TbBool search_target_generic(struct Thing *source, CrInstance inst_idx, struct Thing **targets, short *found_count); +TbBool search_target_ranged_heal(struct Thing *source, CrInstance inst_idx, struct Thing **targets, short *found_count); +/******************************************************************************/ #ifdef __cplusplus } #endif diff --git a/src/creature_states.c b/src/creature_states.c index aa168e7313..698ec61fd9 100644 --- a/src/creature_states.c +++ b/src/creature_states.c @@ -98,6 +98,7 @@ short arrive_at_call_to_arms(struct Thing *creatng); short cleanup_hold_audience(struct Thing *creatng); short creature_being_dropped(struct Thing *creatng); short creature_cannot_find_anything_to_do(struct Thing *creatng); +short creature_casting_preparation(struct Thing *creatng); short creature_change_from_chicken(struct Thing *creatng); short creature_change_to_chicken(struct Thing *creatng); short creature_doing_nothing(struct Thing *creatng); @@ -153,7 +154,6 @@ short state_cleanup_wait_at_door(struct Thing* creatng); short creature_search_for_spell_to_steal_in_room(struct Thing *creatng); short creature_pick_up_spell_to_steal(struct Thing *creatng); short creature_timebomb(struct Thing *creatng); - /******************************************************************************/ #ifdef __cplusplus } @@ -175,8 +175,8 @@ struct StateInfo states[CREATURE_STATES_COUNT] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CrStTyp_Work, 0, 0, 0, 0, 0, 0, 0, 1}, {imp_digs_mines, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CrStTyp_Work, 0, 0, 0, 0, 0, 0, 0, 1}, - {NULL, NULL, NULL, NULL, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CrStTyp_Move, 0, 0, 1, 0, 0, 0, 0, 1}, + {creature_casting_preparation, NULL, NULL, NULL, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, CrStTyp_Work, 0, 1, 0, 0, 0, 0, 0, 1}, {imp_drops_gold, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CrStTyp_Work, 0, 0, 0, 0, 0, 0, 0, 1}, {imp_last_did_job, NULL, NULL, NULL, @@ -1604,6 +1604,37 @@ short creature_cannot_find_anything_to_do(struct Thing *creatng) return 1; } +/** + * @brief process_state function for CreatureCastingPreparation. + * + * @param creatng The creature being updated. + * @return short What we've done to the creature. Enum of CreatureStateReturns. + */ +short creature_casting_preparation(struct Thing *creatng) +{ + struct CreatureControl* cctrl = creature_control_get_from_thing(creatng); + TRACE_THING(creatng); + SYNCDBG(10, "Process %s(%d), kkp act.st: %s, bkp con.st: %s, instance: %s", + thing_model_name(creatng), creatng->index, + creature_state_code_name(cctrl->active_state_bkp), creature_state_code_name(cctrl->continue_state_bkp), + creature_instance_code_name(cctrl->instance_id)); + + if (cctrl->instance_id != CrInst_NULL) + { + // If the spell has not be casted, keep this state. + struct Thing* target = thing_get(cctrl->targtng_idx); + if (target != INVALID_THING) + { + // Try to keep facing to the target. + creature_turn_to_face(creatng, &target->mappos); + } + return CrStRet_Unchanged; + } + internal_set_thing_state(creatng, cctrl->active_state_bkp); + creatng->continue_state = cctrl->continue_state_bkp; + return CrStRet_Modified; +} + void set_creature_size_stuff(struct Thing *creatng) { struct CreatureControl* cctrl = creature_control_get_from_thing(creatng); diff --git a/src/creature_states.h b/src/creature_states.h index 7990953634..c1b830b57d 100644 --- a/src/creature_states.h +++ b/src/creature_states.h @@ -39,7 +39,7 @@ enum CreatureStates { CrSt_ImpArrivesAtMineGold, CrSt_ImpDigsDirt, CrSt_ImpMinesGold, - CrSt_Null6, + CrSt_CreatureCastingPreparation, CrSt_ImpDropsGold, CrSt_ImpLastDidJob, CrSt_ImpArrivesAtImproveDungeon, diff --git a/src/creature_states_combt.c b/src/creature_states_combt.c index 7e53e0c5c4..a2189aa3df 100644 --- a/src/creature_states_combt.c +++ b/src/creature_states_combt.c @@ -1864,7 +1864,6 @@ CrInstance get_self_spell_casting(const struct Thing *thing) if (thing_is_creature_special_digger(thing) && creature_is_doing_digger_activity(thing)) { - // casting wind when under influence of gas if ((cctrl->spell_flags & CSAfF_PoisonCloud) != 0) { @@ -2855,6 +2854,13 @@ short creature_in_combat(struct Thing *creatng) return 0; } CombatState combat_func; + CrInstance instance = process_creature_ranged_buff_spell_casting(creatng); + if (instance != CrInst_NULL) + { + // Return now if the creature has casted something. + return 1; + } + if (cctrl->combat.state_id < sizeof(combat_state)/sizeof(combat_state[0])) combat_func = combat_state[cctrl->combat.state_id]; else diff --git a/src/magic.c b/src/magic.c index 3dd6280d89..bc96a5e055 100644 --- a/src/magic.c +++ b/src/magic.c @@ -607,8 +607,13 @@ void slap_creature(struct PlayerInfo *player, struct Thing *thing) if (thing->active_state != CrSt_CreatureSlapCowers) { clear_creature_instance(thing); - cctrl->active_state_bkp = thing->active_state; - cctrl->continue_state_bkp = thing->continue_state; + if (thing->active_state != CrSt_CreatureCastingPreparation) + { + // If the creature was in CreatureCastingPreparation state, its active and continue + // states have been assigned to those bkp states, so we don't need to assign them again. + cctrl->active_state_bkp = thing->active_state; + cctrl->continue_state_bkp = thing->continue_state; + } creature_mark_if_woken_up(thing); external_set_thing_state(thing, CrSt_CreatureSlapCowers); } diff --git a/src/packets.c b/src/packets.c index a1395ea0be..0a01aa7fc2 100644 --- a/src/packets.c +++ b/src/packets.c @@ -1245,7 +1245,7 @@ void process_players_creature_control_packet_control(long idx) if (!creature_affected_by_spell(cctng, SplK_Chicken)) { inst_inf = creature_instance_info_get(i); - n = get_human_controlled_creature_target(cctng, inst_inf->primary_target); + n = get_human_controlled_creature_target(cctng, i, pckt); set_creature_instance(cctng, i, n, 0); } } @@ -1253,7 +1253,7 @@ void process_players_creature_control_packet_control(long idx) else { inst_inf = creature_instance_info_get(i); - n = get_human_controlled_creature_target(cctng, inst_inf->primary_target); + n = get_human_controlled_creature_target(cctng, i, pckt); set_creature_instance(cctng, i, n, 0); } } @@ -1271,7 +1271,7 @@ void process_players_creature_control_packet_control(long idx) { if (creature_instance_has_reset(cctng, i)) { - n = get_human_controlled_creature_target(cctng, inst_inf->primary_target); + n = get_human_controlled_creature_target(cctng, i, pckt); set_creature_instance(cctng, i, n, 0); } } @@ -1366,7 +1366,7 @@ void process_players_creature_control_packet_action(long plyr_idx) { i = pckt->actn_par1; inst_inf = creature_instance_info_get(i); - k = get_human_controlled_creature_target(thing, inst_inf->primary_target); + k = get_human_controlled_creature_target(thing, i, pckt); set_creature_instance(thing, i, k, 0); if (plyr_idx == my_player_number) { instant_instance_selected(i); @@ -1391,7 +1391,7 @@ void process_players_creature_control_packet_action(long plyr_idx) { i = pckt->actn_par1; inst_inf = creature_instance_info_get(i); - k = get_human_controlled_creature_target(thing, inst_inf->primary_target); + k = get_human_controlled_creature_target(thing, i, pckt); set_creature_instance(thing, i, k, 0); if (plyr_idx == my_player_number) { instant_instance_selected(i); diff --git a/src/thing_creature.c b/src/thing_creature.c index 3511094983..73414c5998 100644 --- a/src/thing_creature.c +++ b/src/thing_creature.c @@ -373,11 +373,11 @@ TbBool load_swipe_graphic_for_creature(const struct Thing *thing) return true; } -/** +/** * Randomise the draw direction of the swipe sprite in the first-person possession view. - * + * * Sets PlayerInfo->swipe_sprite_drawLR to either TRUE or FALSE. - * + * * Draw direction is either: left-to-right (TRUE) or right-to-left (FALSE) */ void randomise_swipe_graphic_direction() @@ -912,7 +912,7 @@ void first_apply_spell_effect_to_thing(struct Thing *thing, SpellKind spell_idx, if (n < 0) { thing->health = 0; - } else + } else { thing->health = min(n, cctrl->max_health); } @@ -1566,7 +1566,7 @@ short creature_take_wage_from_gold_pile(struct Thing *creatng,struct Thing *gold * @param spl_idx Spell index to be casted. * @param shot_lvl Spell level to be casted. */ -void creature_cast_spell_at_thing(struct Thing *castng, struct Thing *targetng, long spl_idx, long shot_lvl) +void creature_cast_spell_at_thing(struct Thing *castng, struct Thing *targetng, SpellKind spl_idx, long shot_lvl) { unsigned char hit_type; if ((castng->alloc_flags & TAlF_IsControlled) != 0) @@ -1776,12 +1776,15 @@ void remove_creature_from_summon_list(struct Dungeon* dungeon, ThingIndex famlrt } } /** - * Casts a spell by caster creature targeted at given coordinates, most likely using shot to transfer the spell. + * @brief Casts a spell by caster creature targeted at given coordinates, most likely using shot to transfer the spell. + * * @param castng The caster creature. * @param spl_idx Spell index to be casted. * @param shot_lvl Spell level to be casted. + * @param trg_x + * @param trg_y */ -void creature_cast_spell(struct Thing *castng, long spl_idx, long shot_lvl, long trg_x, long trg_y) +void creature_cast_spell(struct Thing *castng, SpellKind spl_idx, long shot_lvl, MapSubtlCoord trg_x, MapSubtlCoord trg_y) { long i; const struct SpellConfig* spconf = get_spell_config(spl_idx); @@ -1796,8 +1799,8 @@ void creature_cast_spell(struct Thing *castng, long spl_idx, long shot_lvl, long cctrl->teleport_x = trg_x; cctrl->teleport_y = trg_y; } - // Check if the spell can be fired as a shot - if (spconf->shot_model > 0) + // Check if the spell can be fired as a shot. It is definitely not if casted on itself. + if ((spconf->shot_model > 0) && (cctrl->targtng_idx != castng->index)) { if ((castng->alloc_flags & TAlF_IsControlled) != 0) i = THit_CrtrsNObjcts; @@ -1984,7 +1987,7 @@ void creature_look_for_hidden_doors(struct Thing *creatng) struct Thing* doortng = thing_get(i); if (thing_is_invalid(doortng)) break; - + if (door_is_hidden_to_player(doortng,creatng->owner)) { MapSubtlCoord z = doortng->mappos.z.stl.num; @@ -1998,7 +2001,7 @@ void creature_look_for_hidden_doors(struct Thing *creatng) } else // when closed the door itself blocks sight to the doortng so this checks if open, and in sight - if(creature_can_see_thing(creatng,doortng)) + if(creature_can_see_thing(creatng,doortng)) { reveal_secret_door_to_player(doortng,creatng->owner); } @@ -3408,9 +3411,37 @@ short get_creature_eye_height(const struct Thing *creatng) return (base_height + (base_height * game.conf.crtr_conf.exp.size_increase_on_exp * cctrl->explevel) / 100); } -ThingIndex get_human_controlled_creature_target(struct Thing *thing, long primary_target) +/** + * @brief Get the target of the given instance in the Possession mode. + * + * @param thing The Thing object of the caster. + * @param inst_id The index of the instance be used. + * @param packet The caster's owner's control's or action's packet. This is optional. + * @return ThingIndex The index of the target thing. + */ +ThingIndex get_human_controlled_creature_target(struct Thing *thing, CrInstance inst_id, struct Packet *packet) { ThingIndex index = 0; + struct InstanceInfo *inst_inf = creature_instance_info_get(inst_id); + + if((inst_inf->instance_property_flags & InstPF_SelfBuff) != 0) + { + if ((inst_inf->instance_property_flags & InstPF_RangedBuff) == 0 || + ((packet != NULL) && (packet->additional_packet_values & PCAdV_CrtrContrlPressed) != 0)) + { + // If it doesn't has RANGED_BUFF or the Possession key (default:left shift) is pressed, + // cast on the caster itself. + return thing->index; + } + } + if((inst_inf->instance_property_flags & InstPF_RangedBuff) != 0) + { + if(inst_inf->primary_target != 5 && inst_inf->primary_target != 6) + { + ERRORLOG("The instance %d has RANGED_BUFF property but has no valid primary target.", inst_id); + } + } + struct Thing *i; long angle_xy_to; long angle_difference; @@ -3453,7 +3484,7 @@ ThingIndex get_human_controlled_creature_target(struct Thing *thing, long primar if (i != thing) { TbBool is_valid_target; - switch (primary_target) + switch (inst_inf->primary_target) { case 1: is_valid_target = ((thing_is_creature(i) && !creature_is_being_unconscious(i)) || thing_is_dungeon_heart(i)); @@ -3480,7 +3511,7 @@ ThingIndex get_human_controlled_creature_target(struct Thing *thing, long primar is_valid_target = true; break; default: - ERRORLOG("Illegal primary target type for shot: %d", (int)primary_target); + ERRORLOG("Illegal primary target type for shot: %d", (int)inst_inf->primary_target); is_valid_target = false; break; } @@ -3919,7 +3950,7 @@ void remove_first_creature(struct Thing *creatng) if (!flag_is_set(cctrl->flgfield_2, TF2_Spectator) && !flag_is_set(cctrl->flgfield_2, TF2_SummonedCreature)) { dungeon->owned_creatures_of_model[creatng->model]--; - dungeon->num_active_diggers--; + dungeon->num_active_diggers--; } } else { @@ -5399,11 +5430,11 @@ void check_for_creature_escape_from_lava(struct Thing *thing) */ ThingModel get_footstep_effect_element(struct Thing* thing) { - static const unsigned char tileset_footstep_textures[TEXTURE_VARIATIONS_COUNT] = + static const unsigned char tileset_footstep_textures[TEXTURE_VARIATIONS_COUNT] = { TngEffElm_None, TngEffElm_None, TngEffElm_IceMelt3, TngEffElm_None, TngEffElm_None, TngEffElm_None, TngEffElm_None, TngEffElm_None, TngEffElm_StepSand, TngEffElm_StepGypsum, TngEffElm_None, TngEffElm_None, - TngEffElm_None, TngEffElm_None, TngEffElm_None, TngEffElm_None + TngEffElm_None, TngEffElm_None, TngEffElm_None, TngEffElm_None }; short texture; @@ -5893,7 +5924,13 @@ TngUpdateRet update_creature(struct Thing *thing) { return TUFRet_Deleted; } - process_creature_self_spell_casting(thing); + + if (process_creature_self_spell_casting(thing) == 0) + { + // If this creature didn't cast anything to itself, try to help others. + process_creature_ranged_buff_spell_casting(thing); + } + cctrl->moveaccel.x.val = 0; cctrl->moveaccel.y.val = 0; cctrl->moveaccel.z.val = 0; @@ -6466,7 +6503,7 @@ void controlled_creature_drop_thing(struct Thing *creatng, struct Thing *droptng } } - } + } } } } diff --git a/src/thing_creature.h b/src/thing_creature.h index 51023863b6..78b849d396 100644 --- a/src/thing_creature.h +++ b/src/thing_creature.h @@ -26,6 +26,7 @@ #include "bflib_sprite.h" #include "thing_list.h" #include "map_locations.h" +#include "packets.h" #ifdef __cplusplus extern "C" { @@ -100,7 +101,7 @@ long get_creature_speed(const struct Thing *thing); TbBool control_creature_as_controller(struct PlayerInfo *player, struct Thing *thing); TbBool control_creature_as_passenger(struct PlayerInfo *player, struct Thing *thing); void leave_creature_as_controller(struct PlayerInfo *player, struct Thing *thing); -ThingIndex get_human_controlled_creature_target(struct Thing *thing, long primary_target); +ThingIndex get_human_controlled_creature_target(struct Thing *thing, CrInstance inst_id, struct Packet *packet); struct Thing *get_creature_near_for_controlling(PlayerNumber plyr_idx, MapCoord x, MapCoord y); TbBool load_swipe_graphic_for_creature(const struct Thing *thing); @@ -112,12 +113,14 @@ TbBool set_creature_object_combat(struct Thing *crthing, struct Thing *obthing); TbBool set_creature_object_snipe(struct Thing* crthing, struct Thing* obthing); TbBool set_creature_door_combat(struct Thing *crthing, struct Thing *obthing); void thing_fire_shot(struct Thing *firing,struct Thing *target, ThingModel shot_model, char shot_lvl, unsigned char hit_type); -void creature_cast_spell_at_thing(struct Thing *caster, struct Thing *target, long a3, long a4); +void creature_cast_spell_at_thing(struct Thing *caster, struct Thing *target, SpellKind spl_idx, long shot_lvl); +void creature_cast_spell(struct Thing *caster, SpellKind spl_idx, long shot_lvl, MapSubtlCoord trg_x, MapSubtlCoord trg_y); + void thing_summon_temporary_creature(struct Thing* creatng, ThingModel model, char level, char count, GameTurn duration, long spl_idx); void level_up_familiar(struct Thing* famlrtng); void add_creature_to_summon_list(struct Dungeon* dungeon, ThingIndex famlrtng); void remove_creature_from_summon_list(struct Dungeon* dungeon, ThingIndex famlrtng); -void creature_cast_spell(struct Thing *caster, long trg_x, long trg_y, long a4, long a5); + unsigned int get_creature_blocked_flags_at(struct Thing *thing, struct Coord3d *newpos); struct Thing *get_enemy_soul_container_creature_can_see(struct Thing *thing); diff --git a/src/thing_shots.h b/src/thing_shots.h index ce94cf2e31..2ac1b7cb6c 100644 --- a/src/thing_shots.h +++ b/src/thing_shots.h @@ -62,6 +62,7 @@ enum ShotModels { ShM_WordOfPower, ShM_TrapWordOfPower, ShM_TrapTNT = 32, + ShM_RangedHeal = 33, }; enum ShotFireLogics {