diff --git a/src/include/spells.h b/src/include/spells.h index 44281d0db..74b5773a3 100644 --- a/src/include/spells.h +++ b/src/include/spells.h @@ -235,6 +235,8 @@ typedef struct ConditionInfo { typedef struct { ConditionInfo *Condition; /// Conditions to cast the spell. int Range; /// Max range of the target. + /// Combat mode is when there are hostile non-coward units around + int Combat; /// If it should be casted in combat /// FIXME: Add stuff here for target preference. /// FIXME: Heal units with the lowest hit points first. } AutoCastInfo; diff --git a/src/stratagus/script_spell.cpp b/src/stratagus/script_spell.cpp index 1f8aee882..0b203cabf 100644 --- a/src/stratagus/script_spell.cpp +++ b/src/stratagus/script_spell.cpp @@ -46,56 +46,17 @@ #include "ccl_sound.h" #include "ccl.h" -// ************************************************************************** -// Direct affectation for spell -// ************************************************************************** - -/** -** Parse the dependency of spell. -** list = (upgrade "upgradename") -*/ -local void ccl_spell_dependency(const char *id, SCM list, SpellType *spell) -{ - char *dependencyName; - SCM value; - int dependencyId; - - DebugCheck(!id); - DebugCheck(!spell); - - dependencyName = NULL; - dependencyId = -1; - - value = gh_car(list); - - if (!gh_eq_p(value, gh_symbol2scm("upgrade"))) { - return; - } - list = gh_cdr(list); - value = gh_car(list); - - dependencyName = gh_scm2newstr(value, NULL); - dependencyId = UpgradeIdByIdent(dependencyName); - if (dependencyId == -1) { - // warn user - DebugLevel0Fn("Bad upgrade-name '%s'\n" _C_ dependencyName); - free(dependencyName); - return ; - } - spell->DependencyId = dependencyId; - free(dependencyName); -} - - // ************************************************************************** // Action parsers for spellAction // ************************************************************************** -/** +/* ** Parse the action for spell. -** list = (action-type lots-of-parameters). +** +** @param list SCM list object, with somthing like (action-type params). +** @param spellaction Pointer to spellactopm. */ -local void CclParseSpellAction(SCM list, SpellType* spell, SpellActionType *spellaction) +local void CclSpellAction(SCM list, SpellActionType *spellaction) { char* str; SCM value = list; @@ -324,6 +285,8 @@ local void CclParseSpellAction(SCM list, SpellType* spell, SpellActionType *spel ** @param value scm value to convert. ** ** @return CONDITION_TRUE, CONDITION_FALSE, CONDITION_ONLY or -1 on error. +** @note This is a helper function to make CclSpellCondition shorter +** and easier to understand. */ local char Scm2Condition(SCM value) { @@ -347,7 +310,7 @@ local char Scm2Condition(SCM value) ** ** @notes: conditions must be allocated. All data already in is LOST. */ -local void CclSpellParseCondition(SCM list, ConditionInfo* condition) +local void CclSpellCondition(SCM list, ConditionInfo* condition) { SCM value; @@ -428,41 +391,40 @@ local void CclSpellParseCondition(SCM list, ConditionInfo* condition) } } -/** -** FIXME: docu +/* +** Parse the Condition for spell. +** +** @param list SCM object to parse +** @param autocast pointer to autocast to fill with data. +** +** @notes: autocast must be allocated. All data already in is LOST. */ -local void ccl_spell_autocast(const char *id, SCM list, SpellType *spell) +local void CclSpellAutocast(SCM list, AutoCastInfo* autocast) { SCM value; - int range; - DebugCheck(!id); - DebugCheck(!spell); + DebugCheck(!list); + DebugCheck(!autocast); - value = gh_car(list); - if (!gh_eq_p(value, gh_symbol2scm("range"))) { - return ; + while (!gh_null_p(list)) { + value = gh_car(list); + list = gh_cdr(list); + if (gh_eq_p(value,gh_symbol2scm("range"))) { + autocast->Range=gh_scm2int(gh_car(list)); + list=gh_cdr(list); + } else if (gh_eq_p(value,gh_symbol2scm("combat"))) { + autocast->Combat=Scm2Condition(gh_car(list)); + list=gh_cdr(list); + } else if (gh_eq_p(value,gh_symbol2scm("condition"))) { + if (!autocast->Condition) { + autocast->Condition=(ConditionInfo*)malloc(sizeof(ConditionInfo)); + } + CclSpellCondition(gh_car(list),autocast->Condition); + list=gh_cdr(list); + } else { + errl("Unsupported autocast tag",value); + } } - - list = gh_cdr(list); - value = gh_car(list); - range = gh_scm2int(value); - if (range <= 0 && range != -1/*no limit*/) { - // Warn : range <= 0 have no sens, must be strict positive. or = -1 - return ; - } - spell->AutoCast = malloc(sizeof(*spell->AutoCast)); - memset(spell->AutoCast, 0, sizeof(*spell->AutoCast)); - spell->AutoCast->Range = range; - list = gh_cdr(list); - value = gh_car(list); - if (!gh_eq_p(value, gh_symbol2scm("condition"))) { - return ; - } - list = gh_cdr(list); - value = gh_car(list); - spell->AutoCast->Condition=(ConditionInfo*)malloc(sizeof(ConditionInfo)); - CclSpellParseCondition(value,spell->AutoCast->Condition); } /** @@ -525,15 +487,23 @@ local SCM CclDefineSpell(SCM list) } list=gh_cdr(list); } else if (gh_eq_p(value,gh_symbol2scm("action"))) { - spell->Action=(SpellActionType*)malloc(sizeof(SpellActionType)); - CclParseSpellAction(gh_car(list),spell,spell->Action); + if (!spell->Action) { + spell->Action=(SpellActionType*)malloc(sizeof(SpellActionType)); + } + CclSpellAction(gh_car(list),spell->Action); list=gh_cdr(list); } else if (gh_eq_p(value,gh_symbol2scm("condition"))) { - spell->Conditions=(ConditionInfo*)malloc(sizeof(ConditionInfo)); - CclSpellParseCondition(gh_car(list),spell->Conditions); + if (!spell->Conditions) { + spell->Conditions=(ConditionInfo*)malloc(sizeof(ConditionInfo)); + } + CclSpellCondition(gh_car(list),spell->Conditions); list=gh_cdr(list); } else if (gh_eq_p(value,gh_symbol2scm("autocast"))) { - ccl_spell_autocast("autocast",gh_car(list),spell); + if (!spell->AutoCast) { + spell->AutoCast=(AutoCastInfo*)malloc(sizeof(AutoCastInfo)); + memset(spell->AutoCast,0,sizeof(AutoCastInfo*)); + } + CclSpellAutocast(gh_car(list),spell->AutoCast); list=gh_cdr(list); } else if (gh_eq_p(value,gh_symbol2scm("sound-when-cast"))) { // Free the old name, get the new one @@ -555,11 +525,15 @@ local SCM CclDefineSpell(SCM list) } free(str); list=gh_cdr(list); - } else if (gh_eq_p(value,gh_symbol2scm("depend"))) { - ccl_spell_dependency("depend", gh_car(list), spell); + } else if (gh_eq_p(value,gh_symbol2scm("depend-upgrade"))) { + str = gh_scm2newstr(gh_car(list), NULL); + spell->DependencyId = UpgradeIdByIdent(str); + free(str); + if (spell->DependencyId == -1) { + errl("Bad upgrade name",gh_car(list)); + } list = gh_cdr(list); - } else - { + } else { errl("Unsupported tag", value); } } @@ -591,18 +565,32 @@ local void SaveSpellCondition(CLFile *file,ConditionInfo* condition) DebugCheck(!file); DebugCheck(!condition); - CLprintf(file,"'( "); + CLprintf(file,"( "); // // First save data related to flags. // NOTE: (int) is there to keep compilers happy. // - CLprintf(file,"undead %s ",condstrings[(int)condition->Undead]); - CLprintf(file,"organic %s ",condstrings[(int)condition->Organic]); - CLprintf(file,"hero %s ",condstrings[(int)condition->Hero]); - CLprintf(file,"coward %s ",condstrings[(int)condition->Coward]); - CLprintf(file,"alliance %s ",condstrings[(int)condition->Alliance]); - CLprintf(file,"building %s ",condstrings[(int)condition->Building]); - CLprintf(file,"self %s ",condstrings[(int)condition->TargetSelf]); + if (condition->Undead!=CONDITION_TRUE) { + CLprintf(file,"undead %s ",condstrings[(int)condition->Undead]); + } + if (condition->Organic!=CONDITION_TRUE) { + CLprintf(file,"organic %s ",condstrings[(int)condition->Organic]); + } + if (condition->Hero!=CONDITION_TRUE) { + CLprintf(file,"hero %s ",condstrings[(int)condition->Hero]); + } + if (condition->Coward!=CONDITION_TRUE) { + CLprintf(file,"coward %s ",condstrings[(int)condition->Coward]); + } + if (condition->Alliance!=CONDITION_TRUE) { + CLprintf(file,"alliance %s ",condstrings[(int)condition->Alliance]); + } + if (condition->Building!=CONDITION_TRUE) { + CLprintf(file,"building %s ",condstrings[(int)condition->Building]); + } + if (condition->TargetSelf!=CONDITION_TRUE) { + CLprintf(file,"self %s ",condstrings[(int)condition->TargetSelf]); + } // // Min/Max vital percents // @@ -633,18 +621,18 @@ local void SaveSpellCondition(CLFile *file,ConditionInfo* condition) local void SaveSpellAction(CLFile *file,SpellActionType* action) { if (action->CastFunction==CastAreaBombardment) { - CLprintf(file," '(area-bombardment fields %d shards %d damage %d start-offset-x %d start-offset-y %d)", + CLprintf(file,"(area-bombardment fields %d shards %d damage %d start-offset-x %d start-offset-y %d)", action->Data.AreaBombardment.Fields, action->Data.AreaBombardment.Shards, action->Data.AreaBombardment.Damage, action->Data.AreaBombardment.StartOffsetX, action->Data.AreaBombardment.StartOffsetY); } else if (action->CastFunction==CastFireball) { - CLprintf(file," '(fireball ttl %d damage %d)", + CLprintf(file,"(fireball ttl %d damage %d)", action->Data.Fireball.TTL, action->Data.Fireball.Damage); } else if (action->CastFunction==CastAdjustVitals) { - CLprintf(file," '(adjust-vitals"); + CLprintf(file,"(adjust-vitals"); if (action->Data.AdjustVitals.HP) { CLprintf(file," hit-points %d",action->Data.AdjustVitals.HP); } @@ -656,11 +644,11 @@ local void SaveSpellAction(CLFile *file,SpellActionType* action) } CLprintf(file,")\n"); } else if (action->CastFunction==CastSummon) { - CLprintf(file," '(summon unit-type %s time-to-live %d)", + CLprintf(file,"(summon unit-type %s time-to-live %d)", action->Data.Summon.UnitType->Ident, action->Data.Summon.TTL); } else if (action->CastFunction==CastAdjustBuffs) { - CLprintf(file," '(adjust-buffs"); + CLprintf(file,"(adjust-buffs"); if (action->Data.AdjustBuffs.HasteTicks!=BUFF_NOT_AFFECTED) { CLprintf(file," haste-ticks %d",action->Data.AdjustBuffs.HasteTicks); } @@ -678,27 +666,27 @@ local void SaveSpellAction(CLFile *file,SpellActionType* action) } CLprintf(file,")"); } else if (action->CastFunction==CastPolymorph) { - CLprintf(file," '(polymorph new-form %s)", + CLprintf(file,"(polymorph new-form %s)", action->Data.Polymorph.NewForm->Ident); } else if (action->CastFunction==CastRaiseDead) { - CLprintf(file," '(raise-dead unit-raised %s time-to-live %d)", + CLprintf(file,"(raise-dead unit-raised %s time-to-live %d)", action->Data.RaiseDead.UnitRaised->Ident, action->Data.RaiseDead.TTL); } else if (action->CastFunction==CastFlameShield) { - CLprintf(file," '(flame-shield duration %d)", + CLprintf(file,"(flame-shield duration %d)", action->Data.FlameShield.TTL); } else if (action->CastFunction==CastRunes) { - CLprintf(file," '(runes ttl %d damage %d)", + CLprintf(file,"(runes ttl %d damage %d)", action->Data.Runes.TTL, action->Data.Runes.Damage); } else if (action->CastFunction==CastSpawnPortal) { - CLprintf(file," '(spawn-portal portal-type %s)", + CLprintf(file,"(spawn-portal portal-type %s)", action->Data.SpawnPortal.PortalType->Ident); } else if (action->CastFunction==CastDeathCoil) { - CLprintf(file," '(death-coil)"); + CLprintf(file,"(death-coil)"); // FIXME: more? } else if (action->CastFunction==CastWhirlwind) { - CLprintf(file," '(whirlwind duration %d)", + CLprintf(file,"(whirlwind duration %d)", action->Data.Whirlwind.TTL); // FIXME: more? } @@ -754,20 +742,25 @@ global void SaveSpells(CLFile *file) // // Save the action(effect of the spell) // - CLprintf(file," 'action "); + CLprintf(file," 'action '"); SaveSpellAction(file,spell->Action); CLprintf(file,"\n"); // // FIXME: Save conditions // if (spell->Conditions) { - CLprintf(file," 'condition "); + CLprintf(file," 'condition '"); SaveSpellCondition(file,spell->Conditions); CLprintf(file,"\n"); } // // FIXME: Save autocast and AI info // + if (spell->AutoCast) { + CLprintf(file," 'autocast '(range %d condition ",spell->AutoCast->Range); + SaveSpellCondition(file,spell->Conditions); + CLprintf(file,")\n"); + } CLprintf(file,")\n"); } } diff --git a/src/stratagus/spells.cpp b/src/stratagus/spells.cpp index 7f2d8796a..1a45fd3b4 100644 --- a/src/stratagus/spells.cpp +++ b/src/stratagus/spells.cpp @@ -706,7 +706,7 @@ global int CastAdjustBuffs(Unit *caster, const SpellType *spell, Unit *target, // get mana cost caster->Mana -= spell->ManaCost; - if (spell->Action->Data.AdjustBuffs.SlowTicks!=BUFF_NOT_AFFECTED) { + if (spell->Action->Data.AdjustBuffs.HasteTicks!=BUFF_NOT_AFFECTED) { target->Haste=spell->Action->Data.AdjustBuffs.HasteTicks; } if (spell->Action->Data.AdjustBuffs.SlowTicks!=BUFF_NOT_AFFECTED) { @@ -790,7 +790,7 @@ global int CastAdjustVitals(Unit *caster, const SpellType *spell, Unit *target, DebugCheck(castcount<0); - DebugLevel0Fn("Used to have %d hp and %d mana.\n" _C_ target->HP _C_ target->Mana); + DebugLevel3Fn("Used to have %d hp and %d mana.\n" _C_ target->HP _C_ target->Mana); caster->Mana-=castcount*manacost; if (hp < 0) { @@ -809,7 +809,7 @@ global int CastAdjustVitals(Unit *caster, const SpellType *spell, Unit *target, target->Mana=target->Type->_MaxMana; } - DebugLevel0Fn("Unit now has %d hp and %d mana.\n" _C_ target->HP _C_ target->Mana); + DebugLevel3Fn("Unit now has %d hp and %d mana.\n" _C_ target->HP _C_ target->Mana); PlayGameSound(spell->SoundWhenCast.Sound, MaxSampleVolume); MakeMissile(spell->Missile, x * TileSizeX + TileSizeX / 2, y * TileSizeY + TileSizeY / 2, @@ -1185,7 +1185,7 @@ local int PassCondition(const Unit* caster,const SpellType* spell,const Unit* ta } } if (condition->Alliance!=CONDITION_TRUE) { - if ((condition->Alliance==CONDITION_ONLY)^(IsAllied(caster->Player,target))) { + if ((condition->Alliance==CONDITION_ONLY)^(IsAllied(caster->Player,target)||target->Player==caster->Player)) { return 0; } } @@ -1197,20 +1197,40 @@ local int PassCondition(const Unit* caster,const SpellType* spell,const Unit* ta // // Check vitals now. // - if (condition->MinHpPercent*target->Stats->HitPoints>target->HP) { + if (condition->MinHpPercent*target->Stats->HitPoints/100>target->HP) { return 0; } - if (condition->MaxHpPercent*target->Stats->HitPoints<target->HP) { + if (condition->MaxHpPercent*target->Stats->HitPoints/100<=target->HP) { return 0; } if (target->Type->CanCastSpell) { - if (condition->MinManaPercent*target->Type->_MaxMana>target->Mana) { + if (condition->MinManaPercent*target->Type->_MaxMana/100>target->Mana) { return 0; } - if (condition->MaxManaPercent*target->Type->_MaxMana<target->Mana) { + if (condition->MaxManaPercent*target->Type->_MaxMana/100<target->Mana) { return 0; } } + // + // Check for slow/haste stuff + // This should be used mostly for ai, if you want to keep casting + // slow to no effect I can't see why should we stop you. + // + if (condition->MaxSlowTicks<target->Slow) { + return 0; + } + if (condition->MaxHasteTicks<target->Haste) { + return 0; + } + if (condition->MaxBloodlustTicks<target->Bloodlust) { + return 0; + } + if (condition->MaxInvisibilityTicks<target->Invisible) { + return 0; + } + if (condition->MaxInvincibilityTicks<target->UnholyArmor) { + return 0; + } } return 1; } @@ -1231,59 +1251,98 @@ local Target *SelectTargetUnitsOfAutoCast(const Unit *caster, const SpellType *s int x; int y; int range; - int nb_units; + int nunits; int i; int j; + int combat; DebugCheck(!spell); DebugCheck(!spell->AutoCast); DebugCheck(!caster); + x=caster->X; + y=caster->Y; + range=spell->AutoCast->Range; + + // + // Select all units aroung the caster + // + nunits = SelectUnits(caster->X - range, caster->Y - range, + caster->X + range + caster->Type->TileWidth, caster->Y + range + caster->Type->TileHeight, table); + // + // Check every unit if it is hostile + // + combat=0; + for (i = 0; i < nunits; i++) { + if (IsEnemy(caster->Player,table[i]) && !table[i]->Type->Coward) { + combat=1; + } + } + + // + // Check generic conditions. FIXME: a better way to do this? + // + if (spell->AutoCast->Combat!=CONDITION_TRUE) { + if ((spell->AutoCast->Combat==CONDITION_ONLY)^(combat)) { + return 0; + } + } + switch (spell->Target) { - case TargetSelf : - return NewTargetUnit(caster); case TargetNone : + // TargetNone? return NewTargetNone(); + case TargetSelf : + if (PassCondition(caster, spell, caster, x, y, spell->Conditions) && + PassCondition(caster, spell, caster, x, y, spell->AutoCast->Condition)) { + return NewTargetUnit(caster); + } + return 0; case TargetPosition: - range = spell->AutoCast->Range; - do { - x = caster->X + SyncRand() % (2 * range) - range; - y = caster->Y + SyncRand() % (2 * range) - range; - } while (x < 0 && x <= TheMap.Width - && y < 0 && y <= TheMap.Height); - - // FIXME : CHOOSE a better POSITION (add info in structure ???) - // Just good enough for holyvision... - return NewTargetPosition(x, y); + return 0; + // Autocast with a position? That's hard + // Possibilities: cast reveal-map on a dark region + // Cast raise dead on a bunch of corpses. That would rule. + // Cast summon until out of mana in the heat of battle. Trivial? + // Find a tight group of units and cast area-damage spells. HARD, + // but it is a must-have for AI. What about area-heal? case TargetUnit: - x=caster->X; - y=caster->Y; - range=spell->AutoCast->Range; - // ( + 1) would be ( + caster->size) ?? - nb_units = SelectUnits(caster->X - range, caster->Y - range, - caster->X + range + 1, caster->Y + range + 1,table); - // For all Unit, check if it is a possible target - for (i = 0, j = 0; i < nb_units; i++) { - if (PassCondition(caster,spell,table[i],x,y,spell->Conditions)) { - table[j++] = table[i]; + // + // The units are already selected. + // Check every unit if it is a possible target + // + for (i = 0, j = 0; i < nunits; i++) { + // FIXME: autocast conditions should include normal conditions. + // FIXME: no, really, they should. + if (PassCondition(caster, spell, table[i], x, y, spell->Conditions) && + PassCondition(caster, spell, table[i], x, y, spell->AutoCast->Condition)) { + table[j++] = table[i]; } } - nb_units = j; - if (nb_units != 0) { + nunits = j; + // + // Now select the best unit to target. + // FIXME: Some really smart way to do this. + // FIXME: Heal the unit with the lowest hit-points + // FIXME: Bloodlust the unit with the highest hit-point + // FIMXE: it will survive more + // + if (nunits != 0) { #if 0 -// For the best target + // For the best target??? sort(table, nb_units, spell->autocast->f_order); return NewTargetUnit(table[0]); #else -// For a random valid target - i = SyncRand() % nb_units; + // Best unit, random unit, oh well, same stuff. + i = SyncRand() % nunits; return NewTargetUnit(table[i]); #endif } break; default: - // Error : add the new cases - // FIXME : Warn developpers + // Something is wrong + DebugLevel0Fn("Spell is screwed up, unknown target type\n"); + DebugCheck(1); return NULL; break; } @@ -1473,9 +1532,10 @@ global int AutoCastSpell(Unit *caster, const SpellType *spell) target = NULL; -/* if (PassCondition(caster,spell,target,x,y,spell->Conditions)) + // Check for mana, trivial optimization. + if (caster->Mana<spell->ManaCost) { return 0; - }*/ + } target = SelectTargetUnitsOfAutoCast(caster, spell); if (target == NULL) { return 0;