/** ** Get a player's units in rectangle box specified with 2 coordinates ** ** @param l Lua state. ** ** @return Array of units. */ static int CclGetUnitsAroundUnit(lua_State *l) { const int nargs = lua_gettop(l); if (nargs != 2 && nargs != 3) { LuaError(l, "incorrect argument\n"); } const int slot = LuaToNumber(l, 1); const CUnit &unit = UnitManager.GetSlotUnit(slot); const int range = LuaToNumber(l, 2); bool allUnits = false; if (nargs == 3) { allUnits = LuaToBoolean(l, 3); } lua_newtable(l); std::vector<CUnit *> table; if (allUnits) { SelectAroundUnit(unit, range, table, HasNotSamePlayerAs(Players[PlayerNumNeutral])); } else { SelectAroundUnit(unit, range, table, HasSamePlayerAs(*unit.Player)); } size_t n = 0; for (size_t i = 0; i < table.size(); ++i) { if (table[i]->IsAliveOnMap()) { lua_pushnumber(l, UnitNumber(*table[i])); lua_rawseti(l, -2, ++n); } } return 1; }
/** ** Check if the unit's container has an adjacent unit owned by another non-neutral player ** ** @return true if the unit is now sheltered (or if exited a shelter), false otherwise */ static bool LeaveShelter(CUnit &unit) { if ( !unit.Container || (unit.Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value && unit.Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value) || (!unit.Player->AiEnabled && !(unit.Type->BoolFlag[FAUNA_INDEX].value && unit.Player->Type == PlayerNeutral)) || unit.Container->CanMove() //is a transporter, not a shelter || !unit.Container->Type->CanTransport() //is not a garrisonable building || (unit.Container->Type->BoolFlag[RECRUITHEROES_INDEX].value && unit.Character && unit.Player->Type == PlayerNeutral) //if is a hireable hero in a hero recruitment building, don't leave it ) { return false; } std::vector<CUnit *> table; if (unit.Type->BoolFlag[FAUNA_INDEX].value) { SelectAroundUnit(*unit.Container, 1, table, HasNotSamePlayerAs(*unit.Player)); } else { SelectAroundUnit(*unit.Container, unit.CurrentSightRange, table, MakeAndPredicate(IsEnemyWith(*unit.Player), HasNotSamePlayerAs(Players[PlayerNumNeutral]))); } if (table.size() > 0) { CommandUnload(*unit.Container, unit.Container->tilePos, &unit, FlushCommands, unit.Container->MapLayer->ID); return true; } return false; }
/** ** Attack units in distance. ** ** If the unit can attack must be handled by caller. ** Choose the best target, that can be attacked. ** ** @param unit Find in distance for this unit. ** @param range Distance range to look. ** @param onlyBuildings Search only buildings (useful when attacking with AI force) ** ** @return Unit to be attacked. */ CUnit *AttackUnitsInDistance(const CUnit &unit, int range, CUnitFilter pred) { // if necessary, take possible damage on allied units into account... //Wyrmgus start // if (unit.Type->Missile.Missile->Range > 1 // && (range + unit.Type->Missile.Missile->Range < 15)) { if (unit.GetMissile().Missile->Range > 1 && (range + unit.GetMissile().Missile->Range < 15)) { //Wyrmgus end // If catapult, count units near the target... // FIXME : make it configurable //Wyrmgus start // int missile_range = unit.Type->Missile.Missile->Range + range - 1; int missile_range = unit.GetMissile().Missile->Range + range - 1; //Wyrmgus end Assert(2 * missile_range + 1 < 32); // If unit is removed, use containers x and y const CUnit *firstContainer = unit.Container ? unit.Container : &unit; std::vector<CUnit *> table; SelectAroundUnit(*firstContainer, missile_range, table, //Wyrmgus start // MakeAndPredicate(HasNotSamePlayerAs(Players[PlayerNumNeutral]), pred)); pred); //Wyrmgus end if (table.empty() == false) { return BestRangeTargetFinder(unit, range).Find(table); } return NULL; } else { // If unit is removed, use containers x and y const CUnit *firstContainer = unit.Container ? unit.Container : &unit; std::vector<CUnit *> table; SelectAroundUnit(*firstContainer, range, table, //Wyrmgus start // MakeAndPredicate(HasNotSamePlayerAs(Players[PlayerNumNeutral]), pred)); pred); //Wyrmgus end const int n = static_cast<int>(table.size()); if (range > 25 && table.size() > 9) { std::sort(table.begin(), table.begin() + n, CompareUnitDistance(unit)); } // Find the best unit to attack return BestTargetFinder(unit).Find(table); } }
/** ** PickUpItem ** ** @return true if the unit picks up an item, false otherwise */ static bool PickUpItem(CUnit &unit) { if ( !unit.Type->BoolFlag[ORGANIC_INDEX].value || !unit.Player->AiEnabled ) { return false; } if (unit.Variable[HP_INDEX].Value == unit.GetModifiedVariable(HP_INDEX, VariableMax) && !unit.HasInventory()) { //only look for items to pick up if the unit is damaged or has an inventory return false; } // look for nearby items to pick up std::vector<CUnit *> table; SelectAroundUnit(unit, unit.GetReactionRange(), table); for (size_t i = 0; i != table.size(); ++i) { if (!table[i]->Removed) { if (CanPickUp(unit, *table[i])) { if (table[i]->Variable[HITPOINTHEALING_INDEX].Value > 0 && (unit.GetModifiedVariable(HP_INDEX, VariableMax) - unit.Variable[HP_INDEX].Value) > 0) { if (UnitReachable(unit, *table[i], 1, unit.GetReactionRange() * 8)) { CommandPickUp(unit, *table[i], FlushCommands); return true; } } } } } return false; }
/** ** Player has the quantity of rescued unit-type near to unit-type. */ static int CclIfRescuedNearUnit(lua_State *l) { LuaCheckArgs(l, 5); lua_pushvalue(l, 1); const int plynr = TriggerGetPlayer(l); lua_pop(l, 1); const char *op = LuaToString(l, 2); const int q = LuaToNumber(l, 3); lua_pushvalue(l, 4); const CUnitType *unittype = TriggerGetUnitType(l); lua_pop(l, 1); const CUnitType *ut2 = CclGetUnitType(l); if (!unittype || !ut2) { LuaError(l, "CclIfRescuedNearUnit: not a unit-type valid"); } CompareFunction compare = GetCompareFunction(op); if (!compare) { LuaError(l, "Illegal comparison operation in if-rescued-near-unit: %s" _C_ op); } // Get all unit types 'near'. std::vector<CUnit *> table; FindUnitsByType(*ut2, table); for (size_t i = 0; i != table.size(); ++i) { CUnit ¢erUnit = *table[i]; std::vector<CUnit *> around; SelectAroundUnit(centerUnit, 1, around); // Count the requested units int s = 0; for (size_t j = 0; j != around.size(); ++j) { CUnit &unit = *around[j]; if (unit.RescuedFrom) { // only rescued units // Check unit type if (unittype == ANY_UNIT || (unittype == ALL_FOODUNITS && !unit.Type->Building) || (unittype == ALL_BUILDINGS && unit.Type->Building) || (unittype == unit.Type)) { // Check the player if (plynr == -1 || plynr == unit.Player->Index) { ++s; } } } } if (compare(s, q)) { lua_pushboolean(l, 1); return 1; } } lua_pushboolean(l, 0); return 1; }
void SelectAroundUnit(const CUnit &unit, int range, std::vector<CUnit *> &around) { SelectAroundUnit(unit, range, around, NoFilter()); }
/** ** 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. }
/** ** 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; }
/** ** Cast capture. ** ** @param caster Unit that casts the spell ** @param spell Spell-type pointer ** @param target Target unit that spell is addressed to ** @param goalPos coord of target spot when/if target does not exist ** ** @return =!0 if spell should be repeated, 0 if not */ /* virtual */ int Spell_Capture::Cast(CUnit &caster, const SpellType &spell, CUnit *target, const Vec2i &/*goalPos*/) { if (!target || caster.Player == target->Player) { return 0; } if (this->DamagePercent) { if ((100 * target->Variable[HP_INDEX].Value) / target->Variable[HP_INDEX].Max > this->DamagePercent && target->Variable[HP_INDEX].Value > this->Damage) { HitUnit(&caster, *target, this->Damage); if (this->SacrificeEnable) { // No corpse. caster.Remove(NULL); caster.Release(); } return 1; } } caster.Player->Score += target->Variable[POINTS_INDEX].Value; if (caster.IsEnemy(*target)) { if (target->Type->Building) { caster.Player->TotalRazings++; } else { caster.Player->TotalKills++; } //Wyrmgus start caster.Player->UnitTypeKills[target->Type->Slot]++; /* if (UseHPForXp) { caster.Variable[XP_INDEX].Max += target->Variable[HP_INDEX].Value; } else { caster.Variable[XP_INDEX].Max += target->Variable[POINTS_INDEX].Value; } caster.Variable[XP_INDEX].Value = caster.Variable[XP_INDEX].Max; */ //distribute experience between nearby units belonging to the same player if (!target->Type->BoolFlag[BUILDING_INDEX].value) { std::vector<CUnit *> table; SelectAroundUnit(caster, 6, table, MakeAndPredicate(HasSamePlayerAs(*caster.Player), IsNotBuildingType())); if (UseHPForXp) { caster.Variable[XP_INDEX].Max += target->Variable[HP_INDEX].Value / (table.size() + 1); } else { caster.Variable[XP_INDEX].Max += target->Variable[POINTS_INDEX].Value / (table.size() + 1); } caster.Variable[XP_INDEX].Value = caster.Variable[XP_INDEX].Max; caster.XPChanged(); for (size_t i = 0; i != table.size(); ++i) { if (UseHPForXp) { table[i]->Variable[XP_INDEX].Max += target->Variable[HP_INDEX].Value / (table.size() + 1); } else { table[i]->Variable[XP_INDEX].Max += target->Variable[POINTS_INDEX].Value / (table.size() + 1); } table[i]->Variable[XP_INDEX].Value = table[i]->Variable[XP_INDEX].Max; table[i]->XPChanged(); } } //Wyrmgus end caster.Variable[KILL_INDEX].Value++; caster.Variable[KILL_INDEX].Max++; caster.Variable[KILL_INDEX].Enable = 1; } target->ChangeOwner(*caster.Player); UnitClearOrders(*target); if (this->JoinToAIForce && caster.Player->AiEnabled) { int force = caster.Player->Ai->Force.GetForce(caster); if (force != -1) { caster.Player->Ai->Force[force].Insert(*target); target->GroupId = caster.GroupId; CommandDefend(*target, caster, FlushCommands); } } if (this->SacrificeEnable) { // No corpse. caster.Remove(NULL); caster.Release(); } else { caster.Variable[MANA_INDEX].Value -= spell.ManaCost; } return 0; }