00001
00002
00003
00004
00005
00006
00007
00008
00009
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00030
00031
00032
00033
00034
00035 #include <stdio.h>
00036 #include <stdlib.h>
00037 #include <assert.h>
00038 #include <limits.h>
00039
00040 #include "stratagus.h"
00041 #include "video.h"
00042 #include "sound.h"
00043 #include "unitsound.h"
00044 #include "unittype.h"
00045 #include "actions.h"
00046 #include "player.h"
00047 #include "missile.h"
00048 #include "unit.h"
00049 #include "unit_cache.h"
00050 #include "interface.h"
00051 #include "map.h"
00052 #include "pathfinder.h"
00053
00054
00055
00056
00057
00058
00059
00060
00061 #define PRIORITY_FACTOR 0x00010000
00062 #define HEALTH_FACTOR 0x00000001
00063 #define DISTANCE_FACTOR 0x00100000
00064 #define INRANGE_FACTOR 0x00010000
00065 #define INRANGE_BONUS 0x01000000
00066 #define CANATTACK_BONUS 0x00100000
00067
00068
00069
00070
00071
00072
00073
00074
00075
00085 int FindUnitsByType(const CUnitType *type, CUnit **table, int tablesize)
00086 {
00087 int num = 0;
00088
00089 for (int i = 0; i < NumUnits && num < tablesize; ++i) {
00090 CUnit *unit = Units[i];
00091 if (unit->Type == type && !unit->IsUnusable()) {
00092 table[num++] = unit;
00093 }
00094 }
00095 return num;
00096 }
00097
00108 int FindPlayerUnitsByType(const CPlayer *player, const CUnitType *type,
00109 CUnit **table, int tablesize)
00110 {
00111 int nunits = player->TotalNumUnits;
00112 int typecount = player->UnitTypesCount[type->Slot];
00113 int num = 0;
00114
00115 for (int i = 0; i < nunits && typecount && num < tablesize; ++i) {
00116 CUnit *unit = player->Units[i];
00117 if (unit->Type == type) {
00118 if (!unit->IsUnusable()) {
00119 table[num++] = unit;
00120 }
00121 --typecount;
00122 }
00123 }
00124 return num;
00125 }
00126
00136 CUnit *UnitOnMapTile(int tx, int ty, unsigned type)
00137 {
00138 CUnit *table[UnitMax];
00139 int n;
00140
00141 n = UnitCache.Select(tx, ty, table, UnitMax);
00142 for (int i = 0; i < n; ++i) {
00143
00144
00145 if (table[i]->Type->Vanishes) {
00146 continue;
00147 }
00148 if (type != (unsigned)-1 && (unsigned)table[i]->Type->UnitType != type) {
00149 continue;
00150 }
00151 return table[i];
00152 }
00153
00154 return NoUnitP;
00155 }
00156
00168 CUnit *TargetOnMap(const CUnit *source, int x1, int y1, int x2, int y2)
00169 {
00170 CUnit *table[UnitMax];
00171 CUnit *best = NoUnitP;
00172 int n;
00173
00174 n = UnitCache.Select(x1, y1, x2, y2, table, UnitMax);
00175 for (int i = 0; i < n; ++i) {
00176 CUnit *unit = table[i];
00177 if (!unit->IsVisibleAsGoal(source->Player)) {
00178 continue;
00179 }
00180 if (x2 < unit->X || x1 >= unit->X + unit->Type->TileWidth ||
00181 y2 < unit->Y || y1 >= unit->Y + unit->Type->TileHeight) {
00182 continue;
00183 }
00184 if (!CanTarget(source->Type, unit->Type)) {
00185 continue;
00186 }
00187
00188
00189
00190
00191 if (!best || best->Type->Priority < unit->Type->Priority) {
00192 best = unit;
00193 }
00194 }
00195 return best;
00196 }
00197
00198
00199
00200
00201
00211 CUnit *ResourceOnMap(int tx, int ty, int resource)
00212 {
00213 CUnit *table[UnitMax];
00214 int n;
00215
00216 n = UnitCache.Select(tx, ty, table, UnitMax);
00217 for (int i = 0; i < n; ++i) {
00218 if (table[i]->IsUnusable() || !table[i]->Type->CanHarvestFrom) {
00219 continue;
00220 }
00221 if (resource == -1) {
00222 if (!UnitHoldsResources(table[i])) {
00223 continue;
00224 }
00225 } else {
00226 if (table[i]->ResourcesHeld[resource] == 0) {
00227 continue;
00228 }
00229 }
00230 return table[i];
00231 }
00232 return NoUnitP;
00233 }
00234
00235
00236
00237
00238
00254 static CUnit *FindRangeAttack(const CUnit *u, int range)
00255 {
00256 int x;
00257 int y;
00258 int n;
00259 int cost;
00260 int d;
00261 int effective_hp;
00262 int enemy_count;
00263 int missile_range;
00264 int attackrange;
00265 int hp_damage_evaluate;
00266 int good[32][32];
00267 int bad[32][32];
00268 CUnit *table[UnitMax];
00269 CUnit *dest;
00270 const CUnitType *dtype;
00271 const CUnitType *type;
00272 const CPlayer *player;
00273 int xx;
00274 int yy;
00275 int best_x;
00276 int best_y;
00277 int best_cost;
00278 int i;
00279 int sbad;
00280 int sgood;
00281 CUnit *best;
00282
00283 type = u->Type;
00284 player = u->Player;
00285
00286
00287
00288
00289
00290 missile_range = type->Missile.Missile->Range + range - 1;
00291 attackrange = u->Stats->Variables[ATTACKRANGE_INDEX].Max;
00292
00293 hp_damage_evaluate = u->Stats->Variables[BASICDAMAGE_INDEX].Value
00294 + u->Stats->Variables[PIERCINGDAMAGE_INDEX].Value;
00295
00296 Assert(2 * missile_range + 1 < 32);
00297
00298
00299
00300 if (u->Removed) {
00301 x = u->Container->X;
00302 y = u->Container->Y;
00303 n = UnitCache.Select(x - missile_range, y - missile_range,
00304 x + missile_range + u->Container->Type->TileWidth,
00305 y + missile_range + u->Container->Type->TileHeight, table, UnitMax);
00306 } else {
00307 x = u->X;
00308 y = u->Y;
00309 n = UnitCache.Select(x - missile_range, y - missile_range,
00310 x + missile_range + u->Type->TileWidth,
00311 y + missile_range + u->Type->TileHeight, table, UnitMax);
00312 }
00313
00314 if (!n) {
00315 return NoUnitP;
00316 }
00317
00318 for (y = 0; y < 2 * missile_range + 1; ++y) {
00319 for (x = 0; x < 2 * missile_range + 1; ++x) {
00320 good[y][x] = 0;
00321 bad[y][x] = 0;
00322 }
00323 }
00324
00325 enemy_count = 0;
00326
00327 for (i = 0; i < n; ++i) {
00328 dest = table[i];
00329 dtype = dest->Type;
00330 if (!dest->IsVisibleAsGoal(u->Player)) {
00331 table[i] = 0;
00332 continue;
00333 }
00334
00335
00336 if (!CanTarget(type, dtype)) {
00337 table[i] = 0;
00338 continue;
00339 }
00340
00341 if (!player->IsEnemy(dest)) {
00342 table[i] = 0;
00343
00344
00345
00346
00347
00348
00349
00350 cost = HEALTH_FACTOR * (2 * hp_damage_evaluate - dest->Variable[HP_INDEX].Value) /
00351 (dtype->TileWidth * dtype->TileWidth);
00352 if (cost < 1) {
00353 cost = 1;
00354 }
00355 cost = (-cost);
00356 } else {
00357
00358
00359
00360
00361 cost = 0;
00362
00363
00364
00365 cost += dtype->Priority * PRIORITY_FACTOR;
00366
00367
00368
00369
00370
00371
00372
00373
00374 effective_hp = (dest->Variable[HP_INDEX].Value - 2 * hp_damage_evaluate);
00375
00376
00377
00378
00379 if (effective_hp > 0) {
00380 effective_hp = 0;
00381 }
00382
00383
00384
00385
00386 if (effective_hp < -hp_damage_evaluate) {
00387 effective_hp = -hp_damage_evaluate;
00388 }
00389
00390
00391
00392
00393
00394 cost += -effective_hp * HEALTH_FACTOR;
00395
00396
00397
00398
00399 if (CanTarget(dtype, type)) {
00400 cost += CANATTACK_BONUS;
00401 }
00402
00403
00404
00405
00406 cost = cost / (dtype->TileWidth * dtype->TileWidth);
00407 if (cost < 1) {
00408 cost = 1;
00409 }
00410
00411
00412
00413
00414 if (u->Removed) {
00415 d = MapDistanceBetweenUnits(u->Container, dest);
00416 } else {
00417 d = MapDistanceBetweenUnits(u, dest);
00418 }
00419
00420 if (d <= attackrange || (d <= range && UnitReachable(u, dest, attackrange))) {
00421 ++enemy_count;
00422 } else {
00423 table[i] = 0;
00424 }
00425 }
00426
00427 x = dest->X - u->X + missile_range + 1;
00428 y = dest->Y - u->Y + missile_range + 1;
00429
00430
00431 for (xx = 0; xx < dtype->TileWidth; ++xx) {
00432 for (yy = 0; yy < dtype->TileWidth; ++yy) {
00433 if ((x + xx < 0) || (y + yy < 0) ||
00434 (x + xx >= 2 * missile_range + 1) ||
00435 (y + yy >= 2 * missile_range + 1)) {
00436 continue;
00437 }
00438 if (cost < 0) {
00439 good[y + yy][x + xx] -= cost;
00440 } else {
00441 bad[y + yy][x + xx] += cost;
00442 }
00443 }
00444 }
00445 }
00446
00447 if (!enemy_count) {
00448 return NoUnitP;
00449 }
00450
00451
00452
00453 best_x = -1;
00454 best_y = -1;
00455 best_cost = -1;
00456 best = NoUnitP;
00457 for (i = 0; i < n; ++i) {
00458 if (!table[i]) {
00459 continue;
00460 }
00461 dest = table[i];
00462 dtype = dest->Type;
00463
00464
00465
00466 if (u->X < dest->X) {
00467 x = dest->X;
00468 } else if (u->X > dest->X + dtype->TileWidth - 1) {
00469 x = dest->X + dtype->TileWidth - 1;
00470 } else {
00471 x = u->X;
00472 }
00473
00474 if (u->Y < dest->Y) {
00475 y = dest->Y;
00476 } else if (u->Y > dest->Y + dtype->TileHeight - 1) {
00477 y = dest->Y + dtype->TileHeight - 1;
00478 } else {
00479 y = u->Y;
00480 }
00481
00482
00483 x = x - u->X + missile_range + 1;
00484 y = y - u->Y + missile_range + 1;
00485
00486 sbad = 0;
00487 sgood = 0;
00488 for (yy = -(type->Missile.Missile->Range - 1);
00489 yy <= type->Missile.Missile->Range - 1; ++yy) {
00490 for (xx = -(type->Missile.Missile->Range - 1);
00491 xx <= type->Missile.Missile->Range - 1; ++xx) {
00492 if ((x + xx < 0) || (y + yy < 0) ||
00493 ((x + xx) >= 2 * missile_range + 1) ||
00494 ((y + yy) >= 2 * missile_range + 1)) {
00495 continue;
00496 }
00497
00498 sbad += bad[y + yy][x + xx];
00499 sgood += good[y + yy][x + xx];
00500 if (!yy && !xx) {
00501 sbad += bad[y + yy][x + xx];
00502 sgood += good[y + yy][x + xx];
00503 }
00504 }
00505 }
00506
00507
00508 if (sgood < 20) {
00509 sgood = 20;
00510 }
00511
00512 cost = sbad / sgood;
00513 if (cost > best_cost) {
00514 best_cost = cost;
00515 best = dest;
00516 }
00517 }
00518 return best;
00519 }
00520
00524 static const CUnit *referenceunit;
00525
00531 static int CompareUnitDistance(const void *v1, const void *v2)
00532 {
00533 CUnit *c1 = *(CUnit **)v1;
00534 CUnit *c2 = *(CUnit **)v2;
00535
00536 int d1 = MapDistanceBetweenUnits(referenceunit, c1);
00537 int d2 = MapDistanceBetweenUnits(referenceunit, c2);
00538
00539 if (d1 - d2 != 0) {
00540 return d1 - d2;
00541 } else {
00542 return c1->Slot - c2->Slot;
00543 }
00544 }
00545
00546
00559 CUnit *AttackUnitsInDistance(const CUnit *unit, int range)
00560 {
00561 CUnit *dest;
00562 const CUnitType *type;
00563 const CUnitType *dtype;
00564 CUnit *table[UnitMax];
00565 int x;
00566 int y;
00567 int n;
00568 int i;
00569 int d;
00570 int attackrange;
00571 int cost;
00572 int best_cost;
00573 const CPlayer *player;
00574 CUnit *best_unit;
00575
00576
00577 if (unit->Type->Missile.Missile->Range > 1 &&
00578 (range + unit->Type->Missile.Missile->Range < 15)) {
00579 return FindRangeAttack(unit, range);
00580 }
00581
00582
00583
00584
00585 x = unit->X;
00586 y = unit->Y;
00587 type = unit->Type;
00588 n = UnitCache.Select(x - range, y - range, x + range + type->TileWidth,
00589 y + range + type->TileHeight, table, UnitMax);
00590
00591 if (range > 25 && n > 9) {
00592 referenceunit = unit;
00593 qsort((void*)table, n, sizeof(CUnit*), &CompareUnitDistance);
00594 }
00595
00596 best_unit = NoUnitP;
00597 best_cost = INT_MAX;
00598
00599 player = unit->Player;
00600 attackrange = unit->Stats->Variables[ATTACKRANGE_INDEX].Max;
00601
00602
00603
00604
00605
00606 for (i = 0; i < n; ++i) {
00607 dest = table[i];
00608
00609 if (!dest->IsVisibleAsGoal(unit->Player)) {
00610 continue;
00611 }
00612
00613 if (!player->IsEnemy(dest)) {
00614 continue;
00615 }
00616
00617 dtype = dest->Type;
00618 if (!CanTarget(type, dtype)) {
00619 continue;
00620 }
00621
00622
00623
00624
00625
00626 cost = 0;
00627
00628
00629
00630 cost -= dtype->Priority * PRIORITY_FACTOR;
00631
00632
00633
00634 cost += dest->Variable[HP_INDEX].Value * HEALTH_FACTOR;
00635
00636
00637
00638 d = MapDistanceBetweenUnits(unit, dest);
00639
00640
00641 if (d > range) {
00642 continue;
00643 }
00644 if (d <= attackrange && d >= type->MinAttackRange) {
00645 cost += d * INRANGE_FACTOR;
00646 cost -= INRANGE_BONUS;
00647 } else {
00648 cost += d * DISTANCE_FACTOR;
00649 }
00650
00651
00652
00653 if (CanTarget(dtype, type)) {
00654 cost -= CANATTACK_BONUS;
00655 }
00656
00657
00658
00659
00660 if (cost < best_cost && (d <= attackrange ||
00661 UnitReachable(unit, dest, attackrange))) {
00662 best_unit = dest;
00663 best_cost = cost;
00664 }
00665 }
00666
00667 return best_unit;
00668 }
00669
00677 CUnit *AttackUnitsInRange(const CUnit *unit)
00678 {
00679 Assert(unit->Type->CanAttack);
00680 return AttackUnitsInDistance(unit, unit->Stats->Variables[ATTACKRANGE_INDEX].Max);
00681 }
00682
00690 CUnit *AttackUnitsInReactRange(const CUnit *unit)
00691 {
00692 int range;
00693
00694 Assert(unit->Type->CanAttack);
00695
00696 if (unit->Player->Type == PlayerPerson) {
00697 range = unit->Type->ReactRangePerson;
00698 } else {
00699 range = unit->Type->ReactRangeComputer;
00700 }
00701
00702 return AttackUnitsInDistance(unit, range);
00703 }
00704