/** ** Handle moving to the target. ** ** @param unit Unit, for that the spell cast is handled. */ bool COrder_SpellCast::SpellMoveToTarget(CUnit &unit) { // Unit can't move int err = 1; if (unit.CanMove()) { err = DoActionMove(unit); if (unit.Anim.Unbreakable) { return false; } } // when reached DoActionMove changes unit action // FIXME: use return codes from pathfinder CUnit *goal = this->GetGoal(); if (goal && unit.MapDistanceTo(*goal) <= this->Range) { // there is goal and it is in range UnitHeadingFromDeltaXY(unit, goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos); this->State++; // cast the spell return false; } else if (!goal && unit.MapDistanceTo(this->goalPos) <= this->Range) { // there is no goal and target spot is in range UnitHeadingFromDeltaXY(unit, this->goalPos - unit.tilePos); this->State++; // cast the spell return false; } else if (err == PF_UNREACHABLE || !unit.CanMove()) { // goal/spot unreachable and out of range -- give up return true; } return false; }
/** ** Send a unit building ** ** @param unit pointer to unit. ** @param pos map position to build. ** @param what Unit type to build. ** @param flush if true, flush command queue. */ void CommandBuildBuilding(CUnit &unit, const Vec2i &pos, CUnitType &what, int flush) { if (IsUnitValidForNetwork(unit) == false) { return ; } //Wyrmgus start CMapField &mf = *Map.Field(unit.tilePos); if ((mf.Flags & MapFieldBridge) && !unit.Type->BoolFlag[BRIDGE_INDEX].value && unit.Type->UnitType == UnitTypeLand) { std::vector<CUnit *> table; Select(unit.tilePos, unit.tilePos, table); for (size_t i = 0; i != table.size(); ++i) { if (!table[i]->Removed && table[i]->Type->BoolFlag[BRIDGE_INDEX].value && table[i]->CanMove()) { CommandStopUnit(*table[i]); //always stop the raft if a new command is issued } } } //Wyrmgus end COrderPtr *order; if (unit.Type->Building && !what.BoolFlag[BUILDEROUTSIDE_INDEX].value && unit.MapDistanceTo(pos) > unit.Type->RepairRange) { ClearNewAction(unit); order = &unit.NewOrder; } else { order = GetNextOrder(unit, flush); if (order == NULL) { return; } } *order = COrder::NewActionBuild(unit, pos, what); ClearSavedAction(unit); }
void ResourceUnitFinder::ResourceUnitFinder_Cost::SetFrom(const CUnit &mine, const CUnit *deposit, bool check_usage) { distance = deposit ? mine.MapDistanceTo(*deposit) : 0; if (check_usage) { assigned = mine.Resource.Assigned - mine.Type->MaxOnBoard; waiting = GetNumWaitingWorkers(mine); } else { assigned = 0; waiting = 0; } }
/** ** Send a unit building ** ** @param unit pointer to unit. ** @param pos map position to build. ** @param what Unit type to build. ** @param flush if true, flush command queue. */ void CommandBuildBuilding(CUnit &unit, const Vec2i &pos, CUnitType &what, int flush) { if (IsUnitValidForNetwork(unit) == false) { return ; } COrderPtr *order; if (unit.Type->Building && !what.BuilderOutside && unit.MapDistanceTo(pos) > unit.Type->RepairRange) { ClearNewAction(unit); order = &unit.NewOrder; } else { order = GetNextOrder(unit, flush); if (order == NULL) { return; } } *order = COrder::NewActionBuild(unit, pos, what); ClearSavedAction(unit); }
/** ** Wait for transporter. ** ** @param unit Pointer to unit. ** ** @return True if ship arrived/present, False otherwise. */ bool COrder_Board::WaitForTransporter(CUnit &unit) { if (unit.Wait) { unit.Wait--; return false; } const CUnit *trans = this->GetGoal(); if (!trans || !CanTransport(*trans, unit)) { // FIXME: destination destroyed?? unit.Wait = 6; return false; } if (!trans->IsVisibleAsGoal(*unit.Player)) { DebugPrint("Transporter Gone\n"); this->ClearGoal(); unit.Wait = 6; return false; } if (unit.MapDistanceTo(*trans) == 1) { // enter transporter return true; } // FIXME: any enemies in range attack them, while waiting. // n0b0dy: This means we have to search with a smaller range. // It happens only when you reach the shore,and the transporter // is not there. The unit searches with a big range, so it thinks // it's there. This is why we reset the search. The transporter // should be a lot closer now, so it's not as bad as it seems. this->State = State_Init; this->Range = 1; // Uhh wait a bit. unit.Wait = 10; return false; }
/** ** 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! if (unit.Type->Missile.Missile->Class == MissileClassNone) { // No goal, take target coordinates if (!goal) { if (Map.WallOnMap(goalPos)) { if (Map.HumanWallOnMap(goalPos)) { Map.HitWall(goalPos, CalculateDamageStats(*unit.Stats, *UnitTypeHumanWall->Stats, unit.Variable[BLOODLUST_INDEX].Value)); } else { Map.HitWall(goalPos, CalculateDamageStats(*unit.Stats, *UnitTypeOrcWall->Stats, unit.Variable[BLOODLUST_INDEX].Value)); } return; } DebugPrint("Missile-none hits no unit, shouldn't happen!\n"); return; } HitUnit(&unit, *goal, CalculateDamage(unit, *goal)); 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; }
/** ** 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); }
/* virtual */ void COrder_SpellCast::Execute(CUnit &unit) { COrder_SpellCast &order = *this; if (unit.Wait) { unit.Wait--; return ; } const SpellType &spell = order.GetSpell(); switch (this->State) { case 0: // Check if we can cast the spell. if (!CanCastSpell(unit, spell, order.GetGoal(), order.goalPos)) { // Notify player about this problem if (unit.Variable[MANA_INDEX].Value < spell.ManaCost) { unit.Player->Notify(NotifyYellow, unit.tilePos, _("%s: not enough mana for spell: %s"), unit.Type->Name.c_str(), spell.Name.c_str()); } else { unit.Player->Notify(NotifyYellow, unit.tilePos, _("%s: can't cast spell: %s"), unit.Type->Name.c_str(), spell.Name.c_str()); } if (unit.Player->AiEnabled) { DebugPrint("FIXME: do we need an AI callback?\n"); } this->Finished = true; return ; } if (CheckForDeadGoal(unit)) { return; } // FIXME FIXME FIXME: Check if already in range and skip straight to 2(casting) unit.ReCast = 0; // repeat spell on next pass? (defaults to `no') this->State = 1; // FALL THROUGH case 1: // Move to the target. if (spell.Range && spell.Range != INFINITE_RANGE) { if (SpellMoveToTarget(unit) == true) { this->Finished = true; return ; } return ; } else { this->State = 2; } // FALL THROUGH case 2: // Cast spell on the target. if (!spell.IsCasterOnly()) { AnimateActionSpellCast(unit, *this); if (unit.Anim.Unbreakable) { return ; } } else { // FIXME: what todo, if unit/goal is removed? CUnit *goal = order.GetGoal(); if (goal && goal != &unit && !goal->IsVisibleAsGoal(*unit.Player)) { unit.ReCast = 0; } else { unit.ReCast = SpellCast(unit, spell, goal, order.goalPos); } } // Target is dead ? Change order ? if (CheckForDeadGoal(unit)) { return; } // Check, if goal has moved (for ReCast) if (unit.ReCast && order.GetGoal() && unit.MapDistanceTo(*order.GetGoal()) > this->Range) { this->State = 0; return; } if (!unit.ReCast && unit.CurrentAction() != UnitActionDie) { this->Finished = true; return ; } break; default: this->State = 0; // Reset path, than move to target break; } }
/** ** 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; }
/** ** Handle attacking the target. ** ** @param unit Unit, for that the attack is handled. */ void COrder_Attack::AttackTarget(CUnit &unit) { Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos)); AnimateActionAttack(unit, *this); if (unit.Anim.Unbreakable) { return; } if (!this->HasGoal() && (this->Action == UnitActionAttackGround || Map.WallOnMap(this->goalPos))) { return; } // Target is dead ? Change order ? if (CheckForDeadGoal(unit)) { return; } CUnit *goal = this->GetGoal(); bool dead = !goal || goal->IsAlive() == false; // No target choose one. if (!goal) { goal = AttackUnitsInReactRange(unit); // No new goal, continue way to destination. if (!goal) { // Return to old task ? if (unit.RestoreOrder()) { return; } this->State = MOVE_TO_TARGET; return; } // Save current command to come back. COrder *savedOrder = COrder::NewActionAttack(unit, this->goalPos); if (unit.CanStoreOrder(savedOrder) == false) { delete savedOrder; savedOrder = NULL; } else { unit.SavedOrder = savedOrder; } this->SetGoal(goal); this->goalPos = goal->tilePos; this->MinRange = unit.Type->MinAttackRange; this->Range = unit.Stats->Variables[ATTACKRANGE_INDEX].Max; this->State |= WEAK_TARGET; // Have a weak target, try a better target. // FIXME: if out of range also try another target quick } else { if ((this->State & WEAK_TARGET)) { CUnit *newTarget = AttackUnitsInReactRange(unit); if (newTarget && ThreatCalculate(unit, *newTarget) < ThreatCalculate(unit, *goal)) { if (unit.CanStoreOrder(this)) { unit.SavedOrder = this->Clone(); } goal = newTarget; this->SetGoal(newTarget); this->goalPos = newTarget->tilePos; this->MinRange = unit.Type->MinAttackRange; this->State = MOVE_TO_TARGET; } } } // Still near to target, if not goto target. const int dist = unit.MapDistanceTo(*goal); if (dist > unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { // towers don't chase after goal if (unit.CanMove()) { if (unit.CanStoreOrder(this)) { if (dead) { unit.SavedOrder = COrder::NewActionAttack(unit, this->goalPos); } else { unit.SavedOrder = this->Clone(); } } } unit.Frame = 0; this->State &= WEAK_TARGET; this->State |= MOVE_TO_TARGET; } if (dist < unit.Type->MinAttackRange) { this->State = MOVE_TO_TARGET; } // Turn always to target if (goal) { const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); } }
/** ** Controls moving a unit to its target when attacking ** ** @param unit Unit that is attacking and moving */ void COrder_Attack::MoveToTarget(CUnit &unit) { Assert(!unit.Type->Vanishes && !unit.Destroyed && !unit.Removed); Assert(unit.CurrentOrder() == this); Assert(unit.CanMove()); Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos)); int err = DoActionMove(unit); if (unit.Anim.Unbreakable) { return; } // Look if we have reached the target. if (err == 0 && !this->HasGoal()) { // Check if we're in range when attacking a location and we are waiting if (unit.MapDistanceTo(this->goalPos) <= unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { err = PF_REACHED; } } if (err >= 0) { if (CheckForTargetInRange(unit)) { return; } return; } if (err == PF_REACHED) { CUnit *goal = this->GetGoal(); // Have reached target? FIXME: could use the new return code? if (goal && unit.MapDistanceTo(*goal) <= unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { // Reached another unit, now attacking it const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); this->State++; return; } // Attacking wall or ground. if (((goal && goal->Type && goal->Type->Wall) || (!goal && (Map.WallOnMap(this->goalPos) || this->Action == UnitActionAttackGround))) && unit.MapDistanceTo(this->goalPos) <= unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { // Reached wall or ground, now attacking it UnitHeadingFromDeltaXY(unit, this->goalPos - unit.tilePos); this->State &= WEAK_TARGET; this->State |= ATTACK_TARGET; return; } } // Unreachable. if (err == PF_UNREACHABLE) { if (!this->HasGoal()) { // When attack-moving we have to allow a bigger range this->Range++; unit.Wait = 5; return; } else { this->ClearGoal(); } } // Return to old task? if (!unit.RestoreOrder()) { this->Finished = true; } }
/** ** Start harvesting the resource. ** ** @param unit Pointer to unit. ** ** @return TRUE if ready, otherwise FALSE. */ int COrder_Resource::StartGathering(CUnit &unit) { CUnit *goal; const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource]; Assert(!unit.IX); Assert(!unit.IY); //Wyrmgus start // if (resinfo.TerrainHarvester) { if (Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end // This shouldn't happened? #if 0 if (!Map.IsTerrainResourceOnMap(unit.Orders->goalPos, this->CurrentResource)) { DebugPrint("Wood gone, just like that?\n"); return 0; } #endif UnitHeadingFromDeltaXY(unit, this->goalPos - unit.tilePos); if (resinfo.WaitAtResource) { this->TimeToHarvest = std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]); } else { this->TimeToHarvest = 1; } this->DoneHarvesting = 0; if (this->CurrentResource != unit.CurrentResource) { DropResource(unit); unit.CurrentResource = this->CurrentResource; } return 1; } goal = this->GetGoal(); // Target is dead, stop getting resources. if (!goal || goal->IsVisibleAsGoal(*unit.Player) == false) { // Find an alternative, but don't look too far. this->goalPos.x = -1; this->goalPos.y = -1; if ((goal = UnitFindResource(unit, unit, 15, this->CurrentResource, unit.Player->AiEnabled))) { this->State = SUB_START_RESOURCE; this->SetGoal(goal); } else { this->ClearGoal(); this->Finished = true; } return 0; } // FIXME: 0 can happen, if to near placed by map designer. Assert(unit.MapDistanceTo(*goal) <= 1); // Update the heading of a harvesting unit to looks straight at the resource. //Wyrmgus start // UnitHeadingFromDeltaXY(unit, goal->tilePos - unit.tilePos + goal->Type->GetHalfTileSize()); UnitHeadingFromDeltaXY(unit, Vec2i(goal->tilePos.x * PixelTileSize.x, goal->tilePos.y * PixelTileSize.y) - Vec2i(unit.tilePos.x * PixelTileSize.x, unit.tilePos.y * PixelTileSize.y) + goal->Type->GetHalfTilePixelSize() - unit.Type->GetHalfTilePixelSize()); //Wyrmgus end // If resource is still under construction, wait! if ((goal->Type->MaxOnBoard && goal->Resource.Active >= goal->Type->MaxOnBoard) || goal->CurrentAction() == UnitActionBuilt) { // FIXME: Determine somehow when the resource will be free to use // FIXME: Could we somehow find another resource? Think minerals // FIXME: We should add a flag for that, and a limited range. // However the CPU usage is really low (no pathfinding stuff). unit.Wait = 10; return 0; } // Place unit inside the resource //Wyrmgus start // if (!resinfo.HarvestFromOutside) { if (!goal->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value) { //Wyrmgus end if (goal->Variable[MAXHARVESTERS_INDEX].Value == 0 || goal->Variable[MAXHARVESTERS_INDEX].Value > goal->InsideCount) { this->ClearGoal(); int selected = unit.Selected; unit.Remove(goal); if (selected && !Preference.DeselectInMine) { unit.Removed = 0; SelectUnit(unit); SelectionChanged(); unit.Removed = 1; } } else if (goal->Variable[MAXHARVESTERS_INDEX].Value <= goal->InsideCount) { //Resource is full, wait unit.Wait = 10; return 0; } } if (this->CurrentResource != unit.CurrentResource) { DropResource(unit); unit.CurrentResource = this->CurrentResource; } // Activate the resource goal->Resource.Active++; if (resinfo.WaitAtResource) { //Wyrmgus start // this->TimeToHarvest = std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]); int wait_at_resource = resinfo.WaitAtResource; if (!goal->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value) { wait_at_resource = resinfo.WaitAtResource * 100 / resinfo.ResourceStep; } this->TimeToHarvest = std::max<int>(1, wait_at_resource * SPEEDUP_FACTOR / (unit.Player->SpeedResourcesHarvest[resinfo.ResourceId] + goal->Variable[TIMEEFFICIENCYBONUS_INDEX].Value)); //Wyrmgus end } else { this->TimeToHarvest = 1; } this->DoneHarvesting = 0; return 1; }
/** ** 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; }
/* virtual */ void COrder_Follow::Execute(CUnit &unit) { if (unit.Wait) { if (!unit.Waiting) { unit.Waiting = 1; unit.WaitBackup = unit.Anim; } //Wyrmgus start // UnitShowAnimation(unit, unit.Type->Animations->Still); UnitShowAnimation(unit, unit.GetAnimations()->Still); //Wyrmgus end unit.Wait--; return; } if (unit.Waiting) { unit.Anim = unit.WaitBackup; unit.Waiting = 0; } CUnit *goal = this->GetGoal(); // Reached target if (this->State == State_TargetReached) { if (!goal || !goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("Goal gone\n"); this->Finished = true; return ; } // Don't follow after immobile units if (goal && goal->CanMove() == false) { this->Finished = true; return; } //Wyrmgus start // if (goal->tilePos == this->goalPos) { if (goal->tilePos == this->goalPos && goal->MapLayer == this->MapLayer) { //Wyrmgus end // Move to the next order if (unit.Orders.size() > 1) { this->Finished = true; return ; } unit.Wait = 10; if (this->Range > 1) { this->Range = 1; this->State = State_Init; } return ; } this->State = State_Init; } if (this->State == State_Init) { // first entry this->State = State_Initialized; } switch (DoActionMove(unit)) { // reached end-point? case PF_UNREACHABLE: //Wyrmgus start if ((Map.Field(unit.tilePos, unit.MapLayer)->Flags & MapFieldBridge) && !unit.Type->BoolFlag[BRIDGE_INDEX].value && unit.Type->UnitType == UnitTypeLand) { std::vector<CUnit *> table; Select(unit.tilePos, unit.tilePos, table, unit.MapLayer); for (size_t i = 0; i != table.size(); ++i) { if (!table[i]->Removed && table[i]->Type->BoolFlag[BRIDGE_INDEX].value && table[i]->CanMove()) { if (table[i]->CurrentAction() == UnitActionStill) { CommandStopUnit(*table[i]); CommandMove(*table[i], this->HasGoal() ? this->GetGoal()->tilePos : this->goalPos, FlushCommands, this->HasGoal() ? this->GetGoal()->MapLayer : this->MapLayer); } return; } } } //Wyrmgus end // Some tries to reach the goal this->Range++; break; case PF_REACHED: { if (!goal) { // goal has died this->Finished = true; return ; } // Handle Teleporter Units // FIXME: BAD HACK // goal shouldn't be busy and portal should be alive if (goal->Type->BoolFlag[TELEPORTER_INDEX].value && goal->Goal && goal->Goal->IsAlive() && unit.MapDistanceTo(*goal) <= 1) { if (!goal->IsIdle()) { // wait unit.Wait = 10; return; } // Check if we have enough mana if (goal->Goal->Type->TeleportCost > goal->Variable[MANA_INDEX].Value) { this->Finished = true; return; } else { goal->Variable[MANA_INDEX].Value -= goal->Goal->Type->TeleportCost; } // Everything is OK, now teleport the unit unit.Remove(NULL); if (goal->Type->TeleportEffectIn) { goal->Type->TeleportEffectIn->pushPreamble(); goal->Type->TeleportEffectIn->pushInteger(UnitNumber(unit)); goal->Type->TeleportEffectIn->pushInteger(UnitNumber(*goal)); goal->Type->TeleportEffectIn->pushInteger(unit.GetMapPixelPosCenter().x); goal->Type->TeleportEffectIn->pushInteger(unit.GetMapPixelPosCenter().y); goal->Type->TeleportEffectIn->run(); } unit.tilePos = goal->Goal->tilePos; //Wyrmgus start unit.MapLayer = goal->Goal->MapLayer; //Wyrmgus end DropOutOnSide(unit, unit.Direction, NULL); // FIXME: we must check if the units supports the new order. CUnit &dest = *goal->Goal; if (dest.Type->TeleportEffectOut) { dest.Type->TeleportEffectOut->pushPreamble(); dest.Type->TeleportEffectOut->pushInteger(UnitNumber(unit)); dest.Type->TeleportEffectOut->pushInteger(UnitNumber(dest)); dest.Type->TeleportEffectOut->pushInteger(unit.GetMapPixelPosCenter().x); dest.Type->TeleportEffectOut->pushInteger(unit.GetMapPixelPosCenter().y); dest.Type->TeleportEffectOut->run(); } if (dest.NewOrder == NULL || (dest.NewOrder->Action == UnitActionResource && !unit.Type->BoolFlag[HARVESTER_INDEX].value) //Wyrmgus start // || (dest.NewOrder->Action == UnitActionAttack && !unit.Type->CanAttack) || (dest.NewOrder->Action == UnitActionAttack && !unit.CanAttack(true)) //Wyrmgus end || (dest.NewOrder->Action == UnitActionBoard && unit.Type->UnitType != UnitTypeLand)) { this->Finished = true; return ; } else { if (dest.NewOrder->HasGoal()) { if (dest.NewOrder->GetGoal()->Destroyed) { delete dest.NewOrder; dest.NewOrder = NULL; this->Finished = true; return ; } unit.Orders.insert(unit.Orders.begin() + 1, dest.NewOrder->Clone()); this->Finished = true; return ; } } } this->goalPos = goal->tilePos; //Wyrmgus start this->MapLayer = goal->MapLayer; //Wyrmgus end this->State = State_TargetReached; } // FALL THROUGH default: break; } // Target destroyed? if (goal && !goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("Goal gone\n"); this->goalPos = goal->tilePos + goal->Type->GetHalfTileSize(); //Wyrmgus start this->MapLayer = goal->MapLayer; //Wyrmgus end this->ClearGoal(); goal = NULL; } if (unit.Anim.Unbreakable) { return ; } // If our leader is dead or stops or attacks: // Attack any enemy in reaction range. // If don't set the goal, the unit can than choose a // better goal if moving nearer to enemy. //Wyrmgus start // if (unit.Type->CanAttack if (unit.CanAttack() //Wyrmgus end && (!goal || goal->CurrentAction() == UnitActionAttack || goal->CurrentAction() == UnitActionStill)) { CUnit *target = AttackUnitsInReactRange(unit); if (target) { // Save current command to come back. COrder *savedOrder = NULL; if (unit.CanStoreOrder(unit.CurrentOrder())) { savedOrder = this->Clone(); } this->Finished = true; //Wyrmgus start // unit.Orders.insert(unit.Orders.begin() + 1, COrder::NewActionAttack(unit, target->tilePos)); unit.Orders.insert(unit.Orders.begin() + 1, COrder::NewActionAttack(unit, target->tilePos, target->MapLayer)); //Wyrmgus end if (savedOrder != NULL) { unit.SavedOrder = savedOrder; } } } }
/** ** Unit repairs ** ** @param unit Unit, for that the attack is handled. */ void HandleActionRepair(CUnit &unit) { CUnit *goal; int err; switch (unit.SubAction) { case 0: NewResetPath(unit); unit.SubAction = 1; // FALL THROUGH // // Move near to target. // case 1: // FIXME: RESET FIRST!! Why? We move first and than check if // something is in sight. err = DoActionMove(unit); if (!unit.Anim.Unbreakable) { // // No goal: if meeting damaged building repair it. // goal = unit.CurrentOrder()->GetGoal(); // // Target is dead, choose new one. // // Check if goal is correct unit. if (goal) { if (!goal->IsVisibleAsGoal(unit.Player)) { DebugPrint("repair target gone.\n"); unit.CurrentOrder()->goalPos = goal->tilePos; // FIXME: should I clear this here? unit.CurrentOrder()->ClearGoal(); goal = NULL; NewResetPath(unit); } } else if (unit.Player->AiEnabled) { // Ai players workers should stop if target is killed err = -1; } // // Have reached target? FIXME: could use return value // if (goal && unit.MapDistanceTo(*goal) <= unit.Type->RepairRange && goal->Variable[HP_INDEX].Value < goal->Variable[HP_INDEX].Max) { unit.State = 0; unit.SubAction = 2; unit.Data.Repair.Cycles = 0; const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); } else if (err < 0) { unit.CurrentOrder()->ClearGoal(); if (!unit.RestoreOrder()) { unit.ClearAction(); unit.State = 0; } return; } // FIXME: Should be it already? Assert(unit.CurrentAction() == UnitActionRepair); } break; // // Repair the target. // case 2: AnimateActionRepair(unit); unit.Data.Repair.Cycles++; if (!unit.Anim.Unbreakable) { goal = unit.CurrentOrder()->GetGoal(); // // Target is dead, choose new one. // // Check if goal is correct unit. // FIXME: should I do a function for this? if (goal) { if (!goal->IsVisibleAsGoal(unit.Player)) { DebugPrint("repair goal is gone\n"); unit.CurrentOrder()->goalPos = goal->tilePos; // FIXME: should I clear this here? unit.CurrentOrder()->ClearGoal(); goal = NULL; NewResetPath(unit); } else { int dist = unit.MapDistanceTo(*goal); if (dist <= unit.Type->RepairRange) { RepairUnit(unit, *goal); goal = unit.CurrentOrder()->GetGoal(); } else if (dist > unit.Type->RepairRange) { // If goal has move, chase after it unit.State = 0; unit.SubAction = 0; } } } // // Target is fine, choose new one. // if (!goal || goal->Variable[HP_INDEX].Value >= goal->Variable[HP_INDEX].Max) { unit.CurrentOrder()->ClearGoal(); if (!unit.RestoreOrder()) { unit.ClearAction(); unit.State = 0; } return; } // FIXME: automatic repair } break; } }
/* virtual */ void COrder_Repair::Execute(CUnit &unit) { Assert(this->ReparableTarget == this->GetGoal()); switch (this->State) { case 0: this->State = 1; // FALL THROUGH case 1: { // Move near to target. // FIXME: RESET FIRST!! Why? We move first and than check if // something is in sight. int err = DoActionMove(unit); if (!unit.Anim.Unbreakable) { // No goal: if meeting damaged building repair it. CUnit *goal = this->GetGoal(); if (goal) { if (!goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("repair target gone.\n"); this->goalPos = goal->tilePos + goal->Type->GetHalfTileSize(); ReparableTarget = NULL; this->ClearGoal(); goal = NULL; } } else if (unit.Player->AiEnabled) { // Ai players workers should stop if target is killed err = -1; } // Have reached target? FIXME: could use return value if (goal && unit.MapDistanceTo(*goal) <= unit.Type->RepairRange && goal->Variable[HP_INDEX].Value < goal->Variable[HP_INDEX].Max) { this->State = 2; this->RepairCycle = 0; const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); } else if (err < 0) { this->Finished = true; return ; } } break; } case 2: {// Repair the target. AnimateActionRepair(unit); this->RepairCycle++; if (unit.Anim.Unbreakable) { return ; } CUnit *goal = this->GetGoal(); if (goal) { if (!goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("repair goal is gone\n"); this->goalPos = goal->tilePos + goal->Type->GetHalfTileSize(); // FIXME: should I clear this here? this->ClearGoal(); ReparableTarget = NULL; goal = NULL; } else { const int dist = unit.MapDistanceTo(*goal); if (dist <= unit.Type->RepairRange) { if (RepairUnit(unit, *goal)) { this->Finished = true; return ; } } else if (dist > unit.Type->RepairRange) { // If goal has move, chase after it this->State = 0; } } } // Target is fine, choose new one. if (!goal || goal->Variable[HP_INDEX].Value >= goal->Variable[HP_INDEX].Max) { this->Finished = true; return ; } // FIXME: automatic repair } break; } }