static void DrawUnitInfo_portrait(const CUnit &unit) { const CUnitType &type = *unit.Type; #ifdef USE_MNG if (type.Portrait.Num) { type.Portrait.Mngs[type.Portrait.CurrMng]->Draw( UI.SingleSelectedButton->X, UI.SingleSelectedButton->Y); if (type.Portrait.Mngs[type.Portrait.CurrMng]->iteration == type.Portrait.NumIterations) { type.Portrait.Mngs[type.Portrait.CurrMng]->Reset(); // FIXME: should be configurable if (type.Portrait.CurrMng == 0) { type.Portrait.CurrMng = (SyncRand() % (type.Portrait.Num - 1)) + 1; type.Portrait.NumIterations = 1; } else { type.Portrait.CurrMng = 0; type.Portrait.NumIterations = SyncRand() % 16 + 1; } } return; } #endif if (UI.SingleSelectedButton) { const PixelPos pos(UI.SingleSelectedButton->X, UI.SingleSelectedButton->Y); const int flag = (ButtonAreaUnderCursor == ButtonAreaSelected && ButtonUnderCursor == 0) ? (IconActive | (MouseButtons & LeftButton)) : 0; type.Icon.Icon->DrawUnitIcon(*UI.SingleSelectedButton->Style, flag, pos, "", unit.RescuedFrom ? GameSettings.Presets[unit.RescuedFrom->Index].PlayerColor : GameSettings.Presets[unit.Player->Index].PlayerColor); } }
/** ** Move in a random direction ** ** @return true if the unit moves, false otherwise */ static bool MoveRandomly(CUnit &unit) { if (unit.Type->RandomMovementProbability == false || ((SyncRand() % 100) > unit.Type->RandomMovementProbability)) { return false; } // pick random location Vec2i pos = unit.tilePos; pos.x += SyncRand(unit.Type->RandomMovementDistance * 2 + 1) - unit.Type->RandomMovementDistance; pos.y += SyncRand(unit.Type->RandomMovementDistance * 2 + 1) - unit.Type->RandomMovementDistance; // restrict to map Map.Clamp(pos); // move if possible if (pos != unit.tilePos) { UnmarkUnitFieldFlags(unit); if (UnitCanBeAt(unit, pos)) { MarkUnitFieldFlags(unit); CommandMove(unit, pos, FlushCommands); return true; } MarkUnitFieldFlags(unit); } return false; }
/** ** Calculate hit. ** ** @return whether the target was hit or not. */ static bool CalculateHit(const CUnit &attacker, const CUnitStats &goal_stats, const CUnit *goal) { if (GameSettings.NoRandomness) { return true; } int accuracy = 0; if (attacker.Variable[ACCURACY_INDEX].Value) { accuracy = attacker.Variable[ACCURACY_INDEX].Value; } if (accuracy == 0) { return false; } else { int evasion = 0; if (goal != NULL) { if (goal->Variable[EVASION_INDEX].Value) { evasion = goal->Variable[EVASION_INDEX].Value; } if (goal->Type->BoolFlag[ORGANIC_INDEX].value && !goal->Type->Building && goal->Type->NumDirections == 8) { //flanking if (attacker.Direction == goal->Direction) { evasion -= 4; } else if (goal->Direction == (attacker.Direction - 32) || goal->Direction == (attacker.Direction + 32) || (attacker.Direction == 0 && goal->Direction == 224) || (attacker.Direction == 224 && goal->Direction == 0)) { evasion -= 3; } else if (goal->Direction == (attacker.Direction - 64) || goal->Direction == (attacker.Direction + 64) || (attacker.Direction == 0 && goal->Direction == 192) || (attacker.Direction == 192 && goal->Direction == 0)) { evasion -= 2; } else if (goal->Direction == (attacker.Direction - 96) || goal->Direction == (attacker.Direction + 96) || (attacker.Direction == 0 && goal->Direction == 160) || (attacker.Direction == 160 && goal->Direction == 0)) { evasion -= 1; } } } else { if (goal_stats.Variables[EVASION_INDEX].Value > 0) { evasion = goal_stats.Variables[EVASION_INDEX].Value; } } if (accuracy > 0) { accuracy = SyncRand(accuracy); } if (evasion > 0) { evasion = SyncRand(evasion); } if (evasion > 0 && (accuracy < evasion || accuracy == 0)) { return false; } } return true; }
/* virtual */ void COrder_Still::Execute(CUnit &unit) { // If unit is not bunkered and removed, wait if (unit.Removed //Wyrmgus start // && (unit.Container == nullptr || unit.Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value == false)) { && (unit.Container == nullptr || !unit.Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value || !unit.Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value)) { // make both the unit and the transporter have the tag be necessary for the attack to be possible if (unit.Container != nullptr) { LeaveShelter(unit); // leave shelter if surrounded } //Wyrmgus end return ; } this->Finished = false; switch (this->State) { case SUB_STILL_STANDBY: //Wyrmgus start // UnitShowAnimation(unit, unit.Type->Animations->Still); if (unit.Variable[STUN_INDEX].Value == 0) { //only show the idle animation when still if the unit is not stunned UnitShowAnimation(unit, unit.GetAnimations()->Still); } if (SyncRand(100000) == 0) { PlayUnitSound(unit, VoiceIdle); } unit.StepCount = 0; //Wyrmgus end break; case SUB_STILL_ATTACK: // attacking unit in attack range. AnimateActionAttack(unit, *this); break; } if (unit.Anim.Unbreakable) { // animation can't be aborted here return; } //Wyrmgus start if (unit.Variable[STUN_INDEX].Value > 0) { //if unit is stunned, remain still return; } //Wyrmgus end this->State = SUB_STILL_STANDBY; this->Finished = (this->Action == UnitActionStill); if (this->Action == UnitActionStandGround || unit.Removed || unit.CanMove() == false) { if (unit.AutoCastSpell) { this->AutoCastStand(unit); } if (unit.IsAgressive()) { this->AutoAttackStand(unit); } } else { if (AutoCast(unit) || (unit.IsAgressive() && AutoAttack(unit)) || AutoRepair(unit) //Wyrmgus start // || MoveRandomly(unit)) { || MoveRandomly(unit) || PickUpItem(unit)) { //Wyrmgus end } } }
/* virtual */ void CAnimation_RandomGoto::Action(CUnit &unit, int &/*move*/, int /*scale*/) const { Assert(unit.Anim.Anim == this); if (SyncRand() % 100 < ParseAnimInt(unit, this->randomStr.c_str())) { unit.Anim.Anim = this->gotoLabel; } }
/* virtual */ void CAnimation_RandomSound::Action(CUnit &unit, int &/*move*/, int /*scale*/) const { Assert(unit.Anim.Anim == this); if (unit.IsVisible(*ThisPlayer) || ReplayRevealMap) { const size_t index = SyncRand() % this->sounds.size(); PlayUnitSound(unit, this->sounds[index].Sound); } }
//Wyrmgus start ///* virtual */ int Spell_AreaBombardment::Cast(CUnit &caster, const SpellType &, CUnit *, const Vec2i &goalPos) /* virtual */ int Spell_AreaBombardment::Cast(CUnit &caster, const SpellType &, CUnit *, const Vec2i &goalPos, int z) //Wyrmgus end { int fields = this->Fields; const int shards = this->Shards; const int damage = this->Damage; const PixelDiff offset(this->StartOffsetX, this->StartOffsetY); const MissileType *missile = this->Missile; while (fields--) { Vec2i dpos; // FIXME: radius configurable... do { // find new destination in the map dpos.x = goalPos.x + SyncRand() % 5 - 2; dpos.y = goalPos.y + SyncRand() % 5 - 2; //Wyrmgus start // } while (!Map.Info.IsPointOnMap(dpos)); } while (!Map.Info.IsPointOnMap(dpos, z)); //Wyrmgus end const PixelPos dest = Map.TilePosToMapPixelPos_Center(dpos); const PixelPos start = dest + offset; for (int i = 0; i < shards; ++i) { //Wyrmgus start // ::Missile *mis = MakeMissile(*missile, start, dest); ::Missile *mis = MakeMissile(*missile, start, dest, z); //Wyrmgus end if (mis->Type->BlizzardSpeed) { mis->Delay = i * mis->Type->Sleep * 2 * PixelTileSize.x / mis->Type->BlizzardSpeed; } else if (mis->Type->Speed) { mis->Delay = i * mis->Type->Sleep * 2 * PixelTileSize.x / mis->Type->Speed; } else { mis->Delay = i * mis->Type->Sleep * mis->Type->G->NumFrames; } mis->Damage = damage; // FIXME: not correct -- blizzard should continue even if mage is // destroyed (though it will be quite short time...) mis->SourceUnit = &caster; } } return 1; }
/** ** Loads the Sprite for a unit type ** ** @param type type of unit to load */ void LoadUnitTypeSprite(CUnitType &type) { if (!type.ShadowFile.empty()) { type.ShadowSprite = CGraphic::ForceNew(type.ShadowFile, type.ShadowWidth, type.ShadowHeight); type.ShadowSprite->Load(); if (type.Flip) { type.ShadowSprite->Flip(); } type.ShadowSprite->MakeShadow(); } if (type.Harvester) { for (int i = 0; i < MaxCosts; ++i) { ResourceInfo *resinfo = type.ResInfo[i]; if (!resinfo) { continue; } if (!resinfo->FileWhenLoaded.empty()) { resinfo->SpriteWhenLoaded = CPlayerColorGraphic::New(resinfo->FileWhenLoaded, type.Width, type.Height); resinfo->SpriteWhenLoaded->Load(); if (type.Flip) { resinfo->SpriteWhenLoaded->Flip(); } } if (!resinfo->FileWhenEmpty.empty()) { resinfo->SpriteWhenEmpty = CPlayerColorGraphic::New(resinfo->FileWhenEmpty, type.Width, type.Height); resinfo->SpriteWhenEmpty->Load(); if (type.Flip) { resinfo->SpriteWhenEmpty->Flip(); } } } } if (!type.File.empty()) { type.Sprite = CPlayerColorGraphic::New(type.File, type.Width, type.Height); type.Sprite->Load(); if (type.Flip) { type.Sprite->Flip(); } } #ifdef USE_MNG if (type.Portrait.Num) { for (int i = 0; i < type.Portrait.Num; ++i) { type.Portrait.Mngs[i] = new Mng; type.Portrait.Mngs[i]->Load(type.Portrait.Files[i]); } // FIXME: should be configurable type.Portrait.CurrMng = 0; type.Portrait.NumIterations = SyncRand() % 16 + 1; } #endif }
/** ** Check if we can build the building. ** ** @param type Unit that can build the building. ** @param building Building to be build. ** ** @return True if made, false if can't be made. ** ** @note We must check if the dependencies are fulfilled. */ static int AiBuildBuilding(const CUnitType &type, CUnitType &building, int near_x, int near_y) { CUnit *table[UnitMax]; int num = 0; // // Remove all workers on the way building something // const int nunits = FindPlayerUnitsByType(AiPlayer->Player, type, table); for (int i = 0; i < nunits; ++i) { CUnit& unit = *table[i]; int j; for (j = 0; j < unit.OrderCount; ++j) { int action = unit.Orders[j]->Action; if (action == UnitActionBuild || action == UnitActionRepair || action == UnitActionReturnGoods || (action == UnitActionResource && unit.SubAction > 55) /* SUB_START_GATHERING */) { break; } } if (j == unit.OrderCount) { table[num++] = &unit; } } if (num == 0) { // No workers available to build return 0; } CUnit& unit = (num == 1) ? *table[0] : *table[SyncRand() % num]; Vec2i pos; // Find a place to build. if (AiFindBuildingPlace(unit, building, near_x, near_y, &pos)) { CommandBuildBuilding(unit, pos, building, FlushCommands); return 1; } else { //when first worker can't build then rest also won't be able (save CPU) if (near_x != -1 && near_y != -1) { //Crush CPU !!!!! for (int i = 0; i < num && table[i] != &unit; ++i) { // Find a place to build. if (AiFindBuildingPlace(*table[i], building, near_x, near_y, &pos)) { CommandBuildBuilding(*table[i], pos, building, FlushCommands); return 1; } } } } return 0; }
/** ** Whirlwind controller ** ** @todo do it more configurable. */ void MissileWhirlwind::Action() { // Animate, move. if (!this->AnimWait--) { if (this->NextMissileFrame(1, 0)) { this->SpriteFrame = 0; PointToPointMissile(*this); } this->AnimWait = this->Type->Sleep; } this->Wait = 1; // Center of the tornado const PixelPos pixelCenter = this->position + this->Type->size / 2; const PixelPos centerOffset(PixelTileSize.x / 2, PixelTileSize.y); const Vec2i center = Map.MapPixelPosToTilePos(pixelCenter + centerOffset); //Wyrmgus start Assert(this->Type->AttackSpeed); // if (!(this->TTL % CYCLES_PER_SECOND / 10)) { if (!(this->TTL % CYCLES_PER_SECOND / this->Type->AttackSpeed)) { //AttackSpeed is by default 10 //Wyrmgus end this->MissileHit(); } // Changes direction every 3 seconds (approx.) if (!(this->TTL % 100)) { // missile has reached target unit/spot Vec2i newPos; do { // find new destination in the map newPos.x = center.x + SyncRand() % 5 - 2; newPos.y = center.y + SyncRand() % 5 - 2; } while (!Map.Info.IsPointOnMap(newPos)); this->destination = Map.TilePosToMapPixelPos_Center(newPos); this->source = this->position; this->State = 0; DebugPrint("Whirlwind new direction: %d, %d, TTL: %d\n" _C_ this->destination.x _C_ this->destination.y _C_ this->TTL); } }
/** ** Missile hits the goal. ** ** @param missile Missile hitting the goal. ** @param goal Goal of the missile. ** @param splash Splash damage divisor. */ static void MissileHitsGoal(const Missile &missile, CUnit &goal, int splash) { if (!missile.Type->CanHitOwner && missile.SourceUnit == &goal) { return; } //Wyrmgus start if (CalculateHit(*missile.SourceUnit, *goal.Stats, &goal) == false) { if (splash == 1 && missile.Type->SplashFactor == 0) { return; } else if (splash == 1 && missile.Type->SplashFactor > 0) { splash = missile.Type->SplashFactor; // if missile has splash factor but missed, apply splash damage } } //Wyrmgus end if (goal.CurrentAction() != UnitActionDie) { int damage; if (missile.Type->Damage) { // custom formula Assert(missile.SourceUnit != NULL); damage = CalculateDamage(*missile.SourceUnit, goal, missile.Type->Damage) / splash; } else if (missile.Damage) { // direct damage, spells mostly damage = missile.Damage / splash; } else { Assert(missile.SourceUnit != NULL); damage = CalculateDamage(*missile.SourceUnit, goal, Damage) / splash; } if (missile.Type->Pierce) { // Handle pierce factor for (size_t i = 0; i < (missile.PiercedUnits.size() - 1); ++i) { damage *= (double)missile.Type->ReduceFactor / 100; } } HitUnit(missile.SourceUnit, goal, damage, &missile); //Wyrmgus start //apply Thorns damage if attacker is at melee range if (&goal && goal.Variable[THORNSDAMAGE_INDEX].Value && missile.SourceUnit->MapDistanceTo(goal) <= 1) { int thorns_damage = std::max<int>(goal.Variable[THORNSDAMAGE_INDEX].Value - missile.SourceUnit->Variable[ARMOR_INDEX].Value, 1); if (GameSettings.NoRandomness) { thorns_damage -= ((thorns_damage + 2) / 2) / 2; //if no randomness setting is used, then the damage will always return what would have been the average damage with randomness } else { thorns_damage -= SyncRand() % ((thorns_damage + 2) / 2); } HitUnit(&goal, *missile.SourceUnit, thorns_damage); } //Wyrmgus end } }
/** ** Calculate damage. ** ** @todo NOTE: different targets (big are hit by some missiles better) ** @todo NOTE: lower damage for hidden targets. ** @todo NOTE: lower damage for targets on higher ground. ** ** @param attacker Attacker. ** @param goal Goal unit. ** ** @return damage inflicted on goal. */ static int CalculateDamage(const CUnit *attacker, const CUnit *goal) { int damage; int basic_damage; int piercing_damage; basic_damage = attacker->Stats->Variables[BASICDAMAGE_INDEX].Value; piercing_damage = attacker->Stats->Variables[PIERCINGDAMAGE_INDEX].Value; damage = std::max(basic_damage - goal->Stats->Variables[ARMOR_INDEX].Value, 1); damage += piercing_damage; damage -= SyncRand() % ((damage + 2) / 2); Assert(damage >= 0); return damage; }
/** ** Create a unit and place it on the map ** ** @param l Lua state. ** ** @return Returns the slot number of the made unit. */ static int CclCreateUnit(lua_State *l) { LuaCheckArgs(l, 3); lua_pushvalue(l, 1); CUnitType *unittype = CclGetUnitType(l); if (unittype == NULL) { LuaError(l, "Bad unittype"); } lua_pop(l, 1); Vec2i ipos; CclGetPos(l, &ipos.x, &ipos.y, 3); lua_pushvalue(l, 2); const int playerno = TriggerGetPlayer(l); lua_pop(l, 1); if (playerno == -1) { printf("CreateUnit: You cannot use \"any\" in create-unit, specify a player\n"); LuaError(l, "bad player"); return 0; } if (Players[playerno].Type == PlayerNobody) { printf("CreateUnit: player %d does not exist\n", playerno); LuaError(l, "bad player"); return 0; } CUnit *unit = MakeUnit(*unittype, &Players[playerno]); if (unit == NULL) { DebugPrint("Unable to allocate unit"); return 0; } else { if (UnitCanBeAt(*unit, ipos) || (unit->Type->Building && CanBuildUnitType(NULL, *unit->Type, ipos, 0))) { unit->Place(ipos); } else { const int heading = SyncRand() % 256; unit->tilePos = ipos; DropOutOnSide(*unit, heading, NULL); } UpdateForNewUnit(*unit, 0); lua_pushnumber(l, UnitNumber(*unit)); return 1; } }
/** ** Calculate damage. ** ** Damage calculation: ** (BasicDamage-Armor)+PiercingDamage ** damage =---------------------------------- ** 2 ** damage is multiplied by random 1 or 2. */ local int CalculateDamage(UnitStats* attacker_stats,Unit* goal) { UnitStats* stats; int damage; stats=goal->Stats; damage=-stats->Armor; damage+=attacker_stats->BasicDamage; if( damage<0 ) { damage=0; } damage+=attacker_stats->PiercingDamage+1; // round up damage/=2; damage*=((SyncRand()>>15)&1)+1; DebugLevel3("Damage done %d\n",damage); return damage; }
/** ** Calculate damage. ** ** @todo NOTE: different targets (big are hit by some missiles better) ** @todo NOTE: lower damage for hidden targets. ** @todo NOTE: lower damage for targets on higher ground. ** ** @param attacker_stats Attacker attributes. ** @param goal_stats Goal attributes. ** @param bloodlust If attacker has bloodlust ** @param xp Experience of attacker. ** ** @return damage inflicted to goal. */ static int CalculateDamageStats(const CUnitStats &attacker_stats, const CUnitStats &goal_stats, int bloodlust) { int basic_damage = attacker_stats.Variables[BASICDAMAGE_INDEX].Value; int piercing_damage = attacker_stats.Variables[PIERCINGDAMAGE_INDEX].Value; if (bloodlust) { basic_damage *= 2; piercing_damage *= 2; } int damage = std::max<int>(basic_damage - goal_stats.Variables[ARMOR_INDEX].Value, 1); damage += piercing_damage; damage -= SyncRand() % ((damage + 2) / 2); Assert(damage >= 0); return damage; }
/** ** Check if we can build the building. ** ** @param type Unit that can build the building. ** @param building Building to be build. ** ** @return True if made, false if can't be made. ** ** @note We must check if the dependencies are fulfilled. */ static int AiBuildBuilding(const CUnitType &type, CUnitType &building, const Vec2i &nearPos) { std::vector<CUnit *> table; FindPlayerUnitsByType(*AiPlayer->Player, type, table); int num = 0; // Remove all workers on the way building something for (size_t i = 0; i != table.size(); ++i) { CUnit &unit = *table[i]; if (IsAlreadyWorking(unit) == false) { table[num++] = &unit; } } if (num == 0) { // No workers available to build return 0; } CUnit &unit = (num == 1) ? *table[0] : *table[SyncRand() % num]; Vec2i pos; // Find a place to build. if (AiFindBuildingPlace(unit, building, nearPos, &pos)) { CommandBuildBuilding(unit, pos, building, FlushCommands); return 1; } else { //when first worker can't build then rest also won't be able (save CPU) if (Map.Info.IsPointOnMap(nearPos)) { //Crush CPU !!!!! for (int i = 0; i < num && table[i] != &unit; ++i) { // Find a place to build. if (AiFindBuildingPlace(*table[i], building, nearPos, &pos)) { CommandBuildBuilding(*table[i], pos, building, FlushCommands); return 1; } } } } return 0; }
/** ** Move a unit on map. ** ** @param l Lua state. ** ** @return Returns the slot number of the made placed. */ static int CclMoveUnit(lua_State *l) { LuaCheckArgs(l, 2); lua_pushvalue(l, 1); CUnit *unit = CclGetUnit(l); lua_pop(l, 1); Vec2i ipos; CclGetPos(l, &ipos.x, &ipos.y, 2); if (UnitCanBeAt(*unit, ipos)) { unit->Place(ipos); } else { const int heading = SyncRand() % 256; unit->tilePos = ipos; DropOutOnSide(*unit, heading, NULL); } lua_pushvalue(l, 1); return 1; }
/** ** Select the target for the autocast. ** ** @param caster Unit who would cast the spell. ** @param spell Spell-type pointer. ** ** @return Target* chosen target or Null if spell can't be cast. ** @todo FIXME: should be global (for AI) ??? ** @todo FIXME: write for position target. */ static Target *SelectTargetUnitsOfAutoCast(CUnit &caster, const SpellType &spell) { AutoCastInfo *autocast; // Ai cast should be a lot better. Use autocast if not found. if (caster.Player->AiEnabled && spell.AICast) { autocast = spell.AICast; } else { autocast = spell.AutoCast; } Assert(autocast); const Vec2i &pos = caster.tilePos; int range = autocast->Range; // Select all units aroung the caster std::vector<CUnit *> table; SelectAroundUnit(caster, range, table); // Check generic conditions. FIXME: a better way to do this? if (autocast->Combat != CONDITION_TRUE) { // Check each unit if it is hostile. bool inCombat = false; for (size_t i = 0; i < table.size(); ++i) { const CUnit &target = *table[i]; // Note that CanTarget doesn't take into account (offensive) spells... if (target.IsVisibleAsGoal(*caster.Player) && caster.IsEnemy(target) && (CanTarget(*caster.Type, *target.Type) || CanTarget(*target.Type, *caster.Type))) { inCombat = true; break; } } if ((autocast->Combat == CONDITION_ONLY) ^ (inCombat)) { return NULL; } } switch (spell.Target) { case TargetSelf : if (PassCondition(caster, spell, &caster, pos, spell.Condition) && PassCondition(caster, spell, &caster, pos, autocast->Condition)) { return NewTargetUnit(caster); } return NULL; case TargetPosition: 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: { // The units are already selected. // Check every unit if it is a possible target int n = 0; for (size_t i = 0; i != table.size(); ++i) { // Check if unit in battle if (autocast->Attacker == CONDITION_ONLY) { if (table[i]->CurrentAction() != UnitActionAttack && table[i]->CurrentAction() != UnitActionAttackGround && table[i]->CurrentAction() != UnitActionSpellCast) { continue; } } if (PassCondition(caster, spell, table[i], pos, spell.Condition) && PassCondition(caster, spell, table[i], pos, autocast->Condition)) { table[n++] = table[i]; } } // Now select the best unit to target. if (n != 0) { // For the best target??? if (autocast->PriorytyVar != ACP_NOVALUE) { std::sort(table.begin(), table.begin() + n, AutoCastPrioritySort(caster, autocast->PriorytyVar, autocast->ReverseSort)); return NewTargetUnit(*table[0]); } else { // Use the old behavior return NewTargetUnit(*table[SyncRand() % n]); } } break; } default: // Something is wrong DebugPrint("Spell is screwed up, unknown target type\n"); Assert(0); return NULL; break; } return NULL; // Can't spell the auto-cast. }
/** ** Draw the unit info into top-panel. ** ** @param unit Pointer to unit. */ static void DrawUnitInfo(CUnit *unit) { int i; CUnitType *type; const CUnitStats *stats; int x; int y; CUnit *uins; Assert(unit); UpdateUnitVariables(unit); for (i = 0; i < (int)UI.InfoPanelContents.size(); ++i) { if (CanShowContent(UI.InfoPanelContents[i]->Condition, unit)) { for (std::vector<CContentType *>::const_iterator content = UI.InfoPanelContents[i]->Contents.begin(); content != UI.InfoPanelContents[i]->Contents.end(); ++content) { if (CanShowContent((*content)->Condition, unit)) { (*content)->Draw(unit, UI.InfoPanelContents[i]->DefaultFont); } } } } type = unit->Type; stats = unit->Stats; Assert(type); Assert(stats); // Draw IconUnit #ifdef USE_MNG if (type->Portrait.Num) { type->Portrait.Mngs[type->Portrait.CurrMng]->Draw( UI.SingleSelectedButton->X, UI.SingleSelectedButton->Y); if (type->Portrait.Mngs[type->Portrait.CurrMng]->iteration == type->Portrait.NumIterations) { type->Portrait.Mngs[type->Portrait.CurrMng]->Reset(); // FIXME: should be configurable if (type->Portrait.CurrMng == 0) { type->Portrait.CurrMng = (SyncRand() % (type->Portrait.Num - 1)) + 1; type->Portrait.NumIterations = 1; } else { type->Portrait.CurrMng = 0; type->Portrait.NumIterations = SyncRand() % 16 + 1; } } } else #endif if (UI.SingleSelectedButton) { x = UI.SingleSelectedButton->X; y = UI.SingleSelectedButton->Y; type->Icon.Icon->DrawUnitIcon(unit->Player, UI.SingleSelectedButton->Style, (ButtonAreaUnderCursor == ButtonAreaSelected && ButtonUnderCursor == 0) ? (IconActive | (MouseButtons & LeftButton)) : 0, x, y, ""); } x = UI.InfoPanel.X; y = UI.InfoPanel.Y; // // Show progress if they are selected. // if (NumSelected == 1 && Selected[0] == unit) { // // Building training units. // if (unit->Orders[0]->Action == UnitActionTrain) { if (unit->OrderCount == 1 || unit->Orders[1]->Action != UnitActionTrain) { if (!UI.SingleTrainingText.empty()) { VideoDrawText(UI.SingleTrainingTextX, UI.SingleTrainingTextY, UI.SingleTrainingFont, UI.SingleTrainingText); } if (UI.SingleTrainingButton) { unit->Orders[0]->Type->Icon.Icon->DrawUnitIcon(unit->Player, UI.SingleTrainingButton->Style, (ButtonAreaUnderCursor == ButtonAreaTraining && ButtonUnderCursor == 0) ? (IconActive | (MouseButtons & LeftButton)) : 0, UI.SingleTrainingButton->X, UI.SingleTrainingButton->Y, ""); } } else { if (!UI.TrainingText.empty()) { VideoDrawTextCentered(UI.TrainingTextX, UI.TrainingTextY, UI.TrainingFont, UI.TrainingText); } if (!UI.TrainingButtons.empty()) { for (i = 0; i < unit->OrderCount && i < (int)UI.TrainingButtons.size(); ++i) { if (unit->Orders[i]->Action == UnitActionTrain) { unit->Orders[i]->Type->Icon.Icon->DrawUnitIcon(unit->Player, UI.TrainingButtons[i].Style, (ButtonAreaUnderCursor == ButtonAreaTraining && ButtonUnderCursor == i) ? (IconActive | (MouseButtons & LeftButton)) : 0, UI.TrainingButtons[i].X, UI.TrainingButtons[i].Y, ""); } } } } return; } // // Building upgrading to better type. // if (unit->Orders[0]->Action == UnitActionUpgradeTo) { if (UI.UpgradingButton) { unit->Orders[0]->Type->Icon.Icon->DrawUnitIcon(unit->Player, UI.UpgradingButton->Style, (ButtonAreaUnderCursor == ButtonAreaUpgrading && ButtonUnderCursor == 0) ? (IconActive | (MouseButtons & LeftButton)) : 0, UI.UpgradingButton->X, UI.UpgradingButton->Y, ""); } return; } // // Building research new technology. // if (unit->Orders[0]->Action == UnitActionResearch) { if (UI.ResearchingButton) { unit->Data.Research.Upgrade->Icon->DrawUnitIcon(unit->Player, UI.ResearchingButton->Style, (ButtonAreaUnderCursor == ButtonAreaResearching && ButtonUnderCursor == 0) ? (IconActive | (MouseButtons & LeftButton)) : 0, UI.ResearchingButton->X, UI.ResearchingButton->Y, ""); } return; } } // // Transporting units. // if (type->CanTransport && unit->BoardCount) { int j; uins = unit->UnitInside; for (i = j = 0; i < unit->InsideCount; ++i, uins = uins->NextContained) { if (uins->Boarded && j < (int)UI.TransportingButtons.size()) { uins->Type->Icon.Icon->DrawUnitIcon(unit->Player, UI.TransportingButtons[j].Style, (ButtonAreaUnderCursor == ButtonAreaTransporting && ButtonUnderCursor == j) ? (IconActive | (MouseButtons & LeftButton)) : 0, UI.TransportingButtons[j].X, UI.TransportingButtons[j].Y, ""); UiDrawLifeBar(uins, UI.TransportingButtons[j].X, UI.TransportingButtons[j].Y); if (uins->Type->CanCastSpell && uins->Variable[MANA_INDEX].Max) { UiDrawManaBar(uins, UI.TransportingButtons[j].X, UI.TransportingButtons[j].Y); } if (ButtonAreaUnderCursor == ButtonAreaTransporting && ButtonUnderCursor == j) { UI.StatusLine.Set(uins->Type->Name); } ++j; } } return; } }
//Wyrmgus start //static int CalculateDamageStats(const CUnitStats &attacker_stats, // const CUnitStats &goal_stats, int bloodlust) static int CalculateDamageStats(const CUnit &attacker, const CUnitStats &goal_stats, const CUnit *goal) //Wyrmgus end { //Wyrmgus start // int basic_damage = attacker_stats.Variables[BASICDAMAGE_INDEX].Value; // int piercing_damage = attacker_stats.Variables[PIERCINGDAMAGE_INDEX].Value; int basic_damage = attacker.Variable[BASICDAMAGE_INDEX].Value; int piercing_damage = attacker.Variable[PIERCINGDAMAGE_INDEX].Value; //Wyrmgus end //Wyrmgus start /* if (bloodlust) { basic_damage *= 2; piercing_damage *= 2; } */ int damage_modifier = 100; if (attacker.Variable[BLOODLUST_INDEX].Value > 0) { damage_modifier += 100; } if (attacker.Variable[CRITICALSTRIKECHANCE_INDEX].Value > 0) { if (GameSettings.NoRandomness) { damage_modifier += attacker.Variable[CRITICALSTRIKECHANCE_INDEX].Value; //if no randomness setting is used, then critical strike chance will be used as a constant damage modifier, instead of being a chance of doubling the damage } else { if (SyncRand(100) < attacker.Variable[CRITICALSTRIKECHANCE_INDEX].Value) { damage_modifier += 100; } } } if (goal != NULL) { // extra backstab damage (only works against units (that are organic and non-building, and that have 8 facing directions) facing opposite to the attacker if (attacker.Variable[BACKSTAB_INDEX].Value > 0 && goal->Type->BoolFlag[ORGANIC_INDEX].value && !goal->Type->Building && goal->Type->NumDirections == 8) { if (attacker.Direction == goal->Direction) { damage_modifier += attacker.Variable[BACKSTAB_INDEX].Value; } else if (goal->Direction == (attacker.Direction - 32) || goal->Direction == (attacker.Direction + 32) || (attacker.Direction == 0 && goal->Direction == 224) || (attacker.Direction == 224 && goal->Direction == 0)) { damage_modifier += attacker.Variable[BACKSTAB_INDEX].Value / 2; } } //add bonus against mounted if (attacker.Variable[BONUSAGAINSTMOUNTED_INDEX].Value > 0 && goal->Type->BoolFlag[MOUNTED_INDEX].value) { damage_modifier += attacker.Variable[BONUSAGAINSTMOUNTED_INDEX].Value; } } basic_damage *= damage_modifier; basic_damage /= 100; piercing_damage *= damage_modifier; piercing_damage /= 100; //Wyrmgus end //Wyrmgus start // int damage = std::max<int>(basic_damage - goal_stats.Variables[ARMOR_INDEX].Value, 1); int damage = 0; if (goal != NULL) { damage = std::max<int>(basic_damage - goal->Variable[ARMOR_INDEX].Value, 1); } else { damage = std::max<int>(basic_damage - goal_stats.Variables[ARMOR_INDEX].Value, 1); } //Wyrmgus end damage += piercing_damage; if (GameSettings.NoRandomness) { if (attacker.Variable[ACCURACY_INDEX].Value > 0) { //if no randomness setting is used, and the attacker's accuracy and is greater than 0, then apply accuracy as a damage bonus and evasion as a damage malus if (goal != NULL) { if (goal->Variable[EVASION_INDEX].Value > 0) { damage += attacker.Variable[ACCURACY_INDEX].Value; damage -= goal->Variable[EVASION_INDEX].Value; if (goal->Type->BoolFlag[ORGANIC_INDEX].value && !goal->Type->Building && goal->Type->NumDirections == 8) { //flanking if (attacker.Direction == goal->Direction) { damage += 4; } else if (goal->Direction == (attacker.Direction - 32) || goal->Direction == (attacker.Direction + 32) || (attacker.Direction == 0 && goal->Direction == 224) || (attacker.Direction == 224 && goal->Direction == 0)) { damage += 3; } else if (goal->Direction == (attacker.Direction - 64) || goal->Direction == (attacker.Direction + 64) || (attacker.Direction == 0 && goal->Direction == 192) || (attacker.Direction == 192 && goal->Direction == 0)) { damage += 2; } else if (goal->Direction == (attacker.Direction - 96) || goal->Direction == (attacker.Direction + 96) || (attacker.Direction == 0 && goal->Direction == 160) || (attacker.Direction == 160 && goal->Direction == 0)) { damage += 1; } } } } else { if (goal_stats.Variables[EVASION_INDEX].Value > 0) { damage += attacker.Variable[ACCURACY_INDEX].Value; damage -= goal_stats.Variables[EVASION_INDEX].Value; } } } damage -= ((damage + 2) / 2) / 2; //if no randomness setting is used, then the damage will always return what would have been the average damage with randomness } else { damage -= SyncRand() % ((damage + 2) / 2); } Assert(damage >= 0); return damage; }
/** ** Fire missile. ** ** @param unit Unit that fires the missile. */ void FireMissile(CUnit &unit, CUnit *goal, const Vec2i &goalPos) { Vec2i newgoalPos = goalPos; // Goal dead? if (goal) { Assert(!unit.Type->Missile.Missile->AlwaysFire || unit.Type->Missile.Missile->Range); if (goal->Destroyed) { DebugPrint("destroyed unit\n"); return; } if (goal->Removed) { return; } if (goal->CurrentAction() == UnitActionDie) { if (unit.Type->Missile.Missile->AlwaysFire) { newgoalPos = goal->tilePos; goal = NULL; } else { return; } } } // No missile hits immediately! //Wyrmgus start // if (unit.Type->Missile.Missile->Class == MissileClassNone) { if (unit.Type->Missile.Missile->Class == MissileClassNone || (unit.Type->Animations && unit.Type->Animations->Attack && unit.Type->Animations->RangedAttack && ((goal && unit.MapDistanceTo(*goal) <= 1) || (!goal && unit.MapDistanceTo(goalPos) <= 1)) && !unit.Container)) { // treat melee attacks from units that have both attack and ranged attack animations as having missile class none //Wyrmgus end //Wyrmgus start int damage = 0; //Wyrmgus end // No goal, take target coordinates if (!goal) { if (Map.WallOnMap(goalPos)) { //Wyrmgus start // if (Map.HumanWallOnMap(goalPos)) { if (Map.HumanWallOnMap(goalPos) && CalculateHit(unit, *UnitTypeHumanWall->Stats, NULL) == true) { //Wyrmgus end //Wyrmgus start PlayUnitSound(unit, VoiceHit); damage = CalculateDamageStats(unit, *UnitTypeHumanWall->Stats, NULL); //Wyrmgus end Map.HitWall(goalPos, //Wyrmgus start // CalculateDamageStats(*unit.Stats, // *UnitTypeHumanWall->Stats, unit.Variable[BLOODLUST_INDEX].Value)); damage); //Wyrmgus end //Wyrmgus start // } else { } else if (Map.OrcWallOnMap(goalPos) && CalculateHit(unit, *UnitTypeOrcWall->Stats, NULL) == true) { //Wyrmgus end //Wyrmgus start PlayUnitSound(unit, VoiceHit); damage = CalculateDamageStats(unit, *UnitTypeOrcWall->Stats, NULL); //Wyrmgus end Map.HitWall(goalPos, //Wyrmgus start // CalculateDamageStats(*unit.Stats, // *UnitTypeOrcWall->Stats, unit.Variable[BLOODLUST_INDEX].Value)); damage); //Wyrmgus end } return; } DebugPrint("Missile-none hits no unit, shouldn't happen!\n"); return; } //Wyrmgus start // HitUnit(&unit, *goal, CalculateDamage(unit, *goal, Damage)); if (CalculateHit(unit, *goal->Stats, goal) == true) { damage = CalculateDamage(unit, *goal, Damage); HitUnit(&unit, *goal, damage); PlayUnitSound(unit, VoiceHit); //apply Thorns damage if attacker is at melee range if (goal && goal->Variable[THORNSDAMAGE_INDEX].Value && unit.MapDistanceTo(*goal) <= 1) { int thorns_damage = std::max<int>(goal->Variable[THORNSDAMAGE_INDEX].Value - unit.Variable[ARMOR_INDEX].Value, 1); if (GameSettings.NoRandomness) { thorns_damage -= ((thorns_damage + 2) / 2) / 2; //if no randomness setting is used, then the damage will always return what would have been the average damage with randomness } else { thorns_damage -= SyncRand() % ((thorns_damage + 2) / 2); } HitUnit(goal, unit, thorns_damage); } } else { PlayUnitSound(unit, VoiceMiss); } //Wyrmgus end return; } // If Firing from inside a Bunker CUnit *from = GetFirstContainer(unit); const int dir = ((unit.Direction + NextDirection / 2) & 0xFF) / NextDirection; const PixelPos startPixelPos = Map.TilePosToMapPixelPos_Center(from->tilePos) + unit.Type->MissileOffsets[dir][0]; Vec2i dpos; if (goal) { Assert(goal->Type); // Target invalid? // Moved out of attack range? if (unit.MapDistanceTo(*goal) < unit.Type->MinAttackRange) { DebugPrint("Missile target too near %d,%d\n" _C_ unit.MapDistanceTo(*goal) _C_ unit.Type->MinAttackRange); // FIXME: do something other? return; } // Fire to nearest point of the unit! // If Firing from inside a Bunker if (unit.Container) { NearestOfUnit(*goal, GetFirstContainer(unit)->tilePos, &dpos); } else { dpos = goal->tilePos + goal->Type->GetHalfTileSize(); } } else { dpos = newgoalPos; // FIXME: Can this be too near?? } PixelPos destPixelPos = Map.TilePosToMapPixelPos_Center(dpos); Missile *missile = MakeMissile(*unit.Type->Missile.Missile, startPixelPos, destPixelPos); // // Damage of missile // if (goal) { missile->TargetUnit = goal; } missile->SourceUnit = &unit; }
/** ** Cast demolish ** @param caster Unit that casts the spell ** @param spell Spell-type pointer ** @param target Target unit that spell is addressed to ** @param goalPos tilePos of target spot when/if target does not exist ** ** @return =!0 if spell should be repeated, 0 if not */ /* virtual */ int Spell_Demolish::Cast(CUnit &caster, const SpellType &, CUnit *, const Vec2i &goalPos) { // Allow error margins. (Lame, I know) const Vec2i offset(this->Range + 2, this->Range + 2); //Wyrmgus start // Vec2i minpos = goalPos - offset; // Vec2i maxpos = goalPos + offset; Vec2i minpos = caster.tilePos - offset; Vec2i maxpos = caster.tilePos + Vec2i(caster.Type->TileWidth - 1, caster.Type->TileHeight - 1) + offset; //Wyrmgus end Map.FixSelectionArea(minpos, maxpos); // // Terrain effect of the explosion // //Wyrmgus start /* Vec2i ipos; for (ipos.x = minpos.x; ipos.x <= maxpos.x; ++ipos.x) { for (ipos.y = minpos.y; ipos.y <= maxpos.y; ++ipos.y) { const CMapField &mf = *Map.Field(ipos); if (SquareDistance(ipos, goalPos) > square(this->Range)) { // Not in circle range continue; } else if (mf.isAWall()) { Map.RemoveWall(ipos); } else if (mf.RockOnMap()) { Map.ClearRockTile(ipos); } else if (mf.ForestOnMap()) { Map.ClearWoodTile(ipos); } } } */ if (this->DamageTerrain) { Vec2i ipos; for (ipos.x = minpos.x; ipos.x <= maxpos.x; ++ipos.x) { for (ipos.y = minpos.y; ipos.y <= maxpos.y; ++ipos.y) { const CMapField &mf = *Map.Field(ipos); //Wyrmgus start // if (SquareDistance(ipos, caster.tilePos) > square(this->Range)) { if (caster.MapDistanceTo(ipos) > this->Range) { //Wyrmgus end // Not in circle range continue; } else if (mf.isAWall()) { Map.RemoveWall(ipos); } else if (mf.RockOnMap()) { Map.ClearRockTile(ipos); } else if (mf.ForestOnMap()) { Map.ClearWoodTile(ipos); } } } } //Wyrmgus end // // Effect of the explosion on units. Don't bother if damage is 0 // //Wyrmgus start //if (this->Damage) { if (this->Damage || this->BasicDamage || this->PiercingDamage || this->FireDamage || this->ColdDamage || this->ArcaneDamage || this->LightningDamage || this->AirDamage || this->EarthDamage || this->WaterDamage) { //Wyrmgus end std::vector<CUnit *> table; SelectFixed(minpos, maxpos, table); for (size_t i = 0; i != table.size(); ++i) { CUnit &unit = *table[i]; if (unit.Type->UnitType != UnitTypeFly && unit.IsAlive() //Wyrmgus start // && unit.MapDistanceTo(goalPos) <= this->Range) { // Don't hit flying units! // HitUnit(&caster, unit, this->Damage); && unit.MapDistanceTo(caster) <= this->Range && (UnitNumber(unit) != UnitNumber(caster) || this->DamageSelf) && (caster.IsEnemy(unit) || this->DamageFriendly)) { int damage = 0; if (this->BasicDamage || this->PiercingDamage || this->FireDamage || this->ColdDamage || this->ArcaneDamage || this->LightningDamage || this->AirDamage || this->EarthDamage || this->WaterDamage) { damage = std::max<int>(this->BasicDamage - unit.Variable[ARMOR_INDEX].Value, 1); damage += this->PiercingDamage; //apply resistances if (this->HackDamage) { damage *= 100 - unit.Variable[HACKRESISTANCE_INDEX].Value; damage /= 100; } else if (this->PierceDamage) { damage *= 100 - unit.Variable[PIERCERESISTANCE_INDEX].Value; damage /= 100; } else if (this->BluntDamage) { damage *= 100 - unit.Variable[BLUNTRESISTANCE_INDEX].Value; damage /= 100; } //apply fire and cold damage damage += this->FireDamage * (100 - unit.Variable[FIRERESISTANCE_INDEX].Value) / 100; damage += this->ColdDamage * (100 - unit.Variable[COLDRESISTANCE_INDEX].Value) / 100; damage += this->ArcaneDamage * (100 - unit.Variable[ARCANERESISTANCE_INDEX].Value) / 100; damage += this->LightningDamage * (100 - unit.Variable[LIGHTNINGRESISTANCE_INDEX].Value) / 100; damage += this->AirDamage * (100 - unit.Variable[AIRRESISTANCE_INDEX].Value) / 100; damage += this->EarthDamage * (100 - unit.Variable[EARTHRESISTANCE_INDEX].Value) / 100; damage += this->WaterDamage * (100 - unit.Variable[WATERRESISTANCE_INDEX].Value) / 100; damage -= SyncRand() % ((damage + 2) / 2); } HitUnit(&caster, unit, this->Damage + damage); //Wyrmgus end } } } return 1; }
/** ** Try to move a unit that's in the way */ static void AiMoveUnitInTheWay(CUnit &unit) { static Vec2i dirs[8] = {Vec2i(-1, -1), Vec2i(-1, 0), Vec2i(-1, 1), Vec2i(0, 1), Vec2i(1, 1), Vec2i(1, 0), Vec2i(1, -1), Vec2i(0, -1)}; CUnit *movableunits[16]; Vec2i movablepos[16]; int movablenb; AiPlayer = unit.Player->Ai; // No more than 1 move per 10 cycle ( avoid stressing the pathfinder ) if (GameCycle <= AiPlayer->LastCanNotMoveGameCycle + 10) { return; } const CUnitType &unittype = *unit.Type; const Vec2i u0 = unit.tilePos; const Vec2i u1(u0.x + unittype.TileWidth - 1, u0.y + unittype.TileHeight - 1); movablenb = 0; // Try to make some unit moves around it for (CUnitManager::Iterator it = UnitManager.begin(); it != UnitManager.end(); ++it) { CUnit &blocker = **it; if (blocker.IsUnusable()) { continue; } if (!blocker.CanMove() || blocker.Moving) { continue; } if (blocker.Player != unit.Player && blocker.Player->IsAllied(*unit.Player) == false) { continue; } const CUnitType &blockertype = *blocker.Type; if (blockertype.UnitType != unittype.UnitType) { continue; } const Vec2i b0 = blocker.tilePos; const Vec2i b1(b0.x + blockertype.TileWidth - 1, b0.y + blockertype.TileHeight - 1); if (&unit == &blocker) { continue; } // Check for collision if (unit.MapDistanceTo(blocker) >= unit.Type->TileWidth + 1) { continue; } // Move blocker in a rand dir int r = SyncRand() & 7; int trycount = 8; while (trycount > 0) { r = (r + 1) & 7; --trycount; const Vec2i pos = blocker.tilePos + blocker.Type->TileWidth * dirs[r]; // Out of the map => no ! if (!Map.Info.IsPointOnMap(pos)) { continue; } // move to blocker ? => no ! if (pos == u0) { continue; } if (Map.Field(pos)->UnitCache.size() > 0) { continue; } movableunits[movablenb] = &blocker; movablepos[movablenb] = pos; ++movablenb; trycount = 0; } if (movablenb >= 16) { break; } } // Don't move more than 1 unit. if (movablenb) { const int index = SyncRand() % movablenb; COrder *savedOrder = NULL; if (movableunits[index]->IsIdle() == false) { if (unit.CanStoreOrder(unit.CurrentOrder())) { savedOrder = unit.CurrentOrder()->Clone(); } } CommandMove(*movableunits[index], movablepos[index], FlushCommands); if (savedOrder != NULL) { unit.SavedOrder = savedOrder; } AiPlayer->LastCanNotMoveGameCycle = GameCycle; } }
int ParseAnimInt(const CUnit &unit, const char *parseint) { char s[100]; const CUnit *goal = &unit; if (!strlen(parseint)) { return 0; } strcpy(s, parseint); char *cur = &s[2]; if (s[0] == 'v' || s[0] == 't') { //unit variable detected if (s[0] == 't') { if (unit.CurrentOrder()->HasGoal()) { goal = unit.CurrentOrder()->GetGoal(); } else { return 0; } } char *next = strchr(cur, '.'); if (next == NULL) { fprintf(stderr, "Need also specify the variable '%s' tag \n", cur); ExitFatal(1); } else { *next = '\0'; } const int index = UnitTypeVar.VariableNameLookup[cur];// User variables if (index == -1) { if (!strcmp(cur, "ResourcesHeld")) { return goal->ResourcesHeld; } else if (!strcmp(cur, "ResourceActive")) { return goal->Resource.Active; } else if (!strcmp(cur, "_Distance")) { return unit.MapDistanceTo(*goal); } fprintf(stderr, "Bad variable name '%s'\n", cur); ExitFatal(1); } if (!strcmp(next + 1, "Value")) { return goal->Variable[index].Value; } else if (!strcmp(next + 1, "Max")) { return goal->Variable[index].Max; } else if (!strcmp(next + 1, "Increase")) { return goal->Variable[index].Increase; } else if (!strcmp(next + 1, "Enable")) { return goal->Variable[index].Enable; } else if (!strcmp(next + 1, "Percent")) { return goal->Variable[index].Value * 100 / goal->Variable[index].Max; } return 0; } else if (s[0] == 'b' || s[0] == 'g') { //unit bool flag detected if (s[0] == 'g') { if (unit.CurrentOrder()->HasGoal()) { goal = unit.CurrentOrder()->GetGoal(); } else { return 0; } } const int index = UnitTypeVar.BoolFlagNameLookup[cur];// User bool flags if (index == -1) { fprintf(stderr, "Bad bool-flag name '%s'\n", cur); ExitFatal(1); } return goal->Type->BoolFlag[index].value; } else if (s[0] == 's') { //spell type detected Assert(goal->CurrentAction() == UnitActionSpellCast); const COrder_SpellCast &order = *static_cast<COrder_SpellCast *>(goal->CurrentOrder()); const SpellType &spell = order.GetSpell(); if (!strcmp(spell.Ident.c_str(), cur)) { return 1; } return 0; } else if (s[0] == 'S') { // check if autocast for this spell available const SpellType *spell = SpellTypeByIdent(cur); if (!spell) { fprintf(stderr, "Invalid spell: '%s'\n", cur); ExitFatal(1); } if (unit.AutoCastSpell[spell->Slot]) { return 1; } return 0; } else if (s[0] == 'p') { //player variable detected char *next; if (*cur == '(') { ++cur; char *end = strchr(cur, ')'); if (end == NULL) { fprintf(stderr, "ParseAnimInt: expected ')'\n"); ExitFatal(1); } *end = '\0'; next = end + 1; } else { next = strchr(cur, '.'); } if (next == NULL) { fprintf(stderr, "Need also specify the %s player's property\n", cur); ExitFatal(1); } else { *next = '\0'; } char *arg = strchr(next + 1, '.'); if (arg != NULL) { *arg = '\0'; } return GetPlayerData(ParseAnimPlayer(unit, cur), next + 1, arg + 1); } else if (s[0] == 'r') { //random value char *next = strchr(cur, '.'); if (next == NULL) { return SyncRand(atoi(cur) + 1); } else { *next = '\0'; const int min = atoi(cur); return min + SyncRand(atoi(next + 1) - min + 1); } } else if (s[0] == 'l') { //player number return ParseAnimPlayer(unit, cur); } // Check if we trying to parse a number Assert(isdigit(s[0]) || s[0] == '-'); return atoi(parseint); }
/** ** Try to move a unit that's in the way */ static void AiMoveUnitInTheWay(CUnit *unit) { static int dirs[8][2] = {{-1,-1},{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1}}; int ux0; int uy0; int ux1; int uy1; int bx0; int by0; int bx1; int by1; int x; int y; int trycount,i; CUnit *blocker; CUnitType *unittype; CUnitType *blockertype; CUnit *movableunits[16]; int movablepos[16][2]; int movablenb; AiPlayer = unit->Player->Ai; unittype = unit->Type; ux0 = unit->X; uy0 = unit->Y; ux1 = ux0 + unittype->TileWidth - 1; uy1 = uy0 + unittype->TileHeight - 1; movablenb = 0; // Try to make some unit moves around it for (i = 0; i < NumUnits; ++i) { blocker = Units[i]; if (blocker->IsUnusable()) { continue; } if (!blocker->IsIdle()) { continue; } if (blocker->Player != unit->Player) { // Not allied if (!(blocker->Player->Allied & (1 << unit->Player->Index))) { continue; } } blockertype = blocker->Type; if (blockertype->UnitType != unittype->UnitType) { continue; } if (!CanMove(blocker)) { continue; } bx0 = blocker->X; by0 = blocker->Y; bx1 = bx0 + blocker->Type->TileWidth - 1; by1 = by0 + blocker->Type->TileHeight - 1;; // Check for collision if (!((ux0 == bx1 + 1 || ux1 == bx0 - 1) && (std::max(by0, uy0) <= std::min(by1, uy1))) && !((uy0 == by1 + 1 || uy1 == by0 - 1) && (std::max(bx0, ux0) <= std::min(bx1, ux1)))) { continue; } if (unit == blocker) { continue; } // Move blocker in a rand dir i = SyncRand() & 7; trycount = 8; while (trycount > 0) { i = (i + 1) & 7; --trycount; x = blocker->X + dirs[i][0]; y = blocker->Y + dirs[i][1]; // Out of the map => no ! if (x < 0 || y < 0 || x >= Map.Info.MapWidth || y >= Map.Info.MapHeight) { continue; } // move to blocker ? => no ! if (x == ux0 && y == uy0) { continue; } movableunits[movablenb] = blocker; movablepos[movablenb][0] = x; movablepos[movablenb][1] = y; ++movablenb; trycount = 0; } if (movablenb >= 16) { break; } } // Don't move more than 1 unit. if (movablenb) { i = SyncRand() % movablenb; CommandMove(movableunits[i], movablepos[i][0], movablepos[i][1], FlushCommands); } }
/** ** Move in a random direction ** ** @return true if the unit moves, false otherwise */ static bool MoveRandomly(CUnit &unit) { if (!unit.Type->RandomMovementProbability || SyncRand(100) > unit.Type->RandomMovementProbability) { return false; } // pick random location Vec2i pos = unit.tilePos; pos.x += SyncRand(unit.Type->RandomMovementDistance * 2 + 1) - unit.Type->RandomMovementDistance; pos.y += SyncRand(unit.Type->RandomMovementDistance * 2 + 1) - unit.Type->RandomMovementDistance; // restrict to map Map.Clamp(pos, unit.MapLayer->ID); // move if possible if (pos != unit.tilePos) { UnmarkUnitFieldFlags(unit); if (UnitCanBeAt(unit, pos, unit.MapLayer->ID)) { MarkUnitFieldFlags(unit); //Wyrmgus start //prefer terrains which this unit's species is native to; only go to other ones if is already in a non-native terrain type if (unit.Type->Species && std::find(unit.Type->Species->Terrains.begin(), unit.Type->Species->Terrains.end(), Map.GetTileTopTerrain(unit.tilePos, false, unit.MapLayer->ID)) != unit.Type->Species->Terrains.end()) { if (std::find(unit.Type->Species->Terrains.begin(), unit.Type->Species->Terrains.end(), Map.GetTileTopTerrain(pos, false, unit.MapLayer->ID)) == unit.Type->Species->Terrains.end()) { return false; } } if (unit.Type->BoolFlag[PEOPLEAVERSION_INDEX].value) { std::vector<CUnit *> table; SelectAroundUnit(unit, std::max(6, unit.Type->RandomMovementDistance), table, HasNotSamePlayerAs(*unit.Player)); if (!table.size()) { //only avoid going near a settled area if isn't already surrounded by civilizations' units //don't go near settled areas Vec2i minpos = pos; Vec2i maxpos = pos; minpos.x = pos.x - std::max(6, unit.Type->RandomMovementDistance); minpos.y = pos.y - std::max(6, unit.Type->RandomMovementDistance); maxpos.x = pos.x + std::max(6, unit.Type->RandomMovementDistance); maxpos.y = pos.y + std::max(6, unit.Type->RandomMovementDistance); std::vector<CUnit *> second_table; Select(minpos, maxpos, second_table, unit.MapLayer->ID, HasNotSamePlayerAs(*unit.Player)); if (second_table.size() > 0) { return false; } } else { //even if is already in a settled area, don't go to places adjacent to units owned by players other than the neutral player Vec2i minpos = pos; Vec2i maxpos = pos; minpos.x = pos.x - 1; minpos.y = pos.y - 1; maxpos.x = pos.x + 1; maxpos.y = pos.y + 1; std::vector<CUnit *> second_table; Select(minpos, maxpos, second_table, unit.MapLayer->ID, HasNotSamePlayerAs(*unit.Player)); if (second_table.size() > 0) { return false; } } } CommandMove(unit, pos, FlushCommands, unit.MapLayer->ID); return true; } MarkUnitFieldFlags(unit); } return false; }