/** * Kneel/Standup. * @param action Pointer to an action. */ void BattlescapeState::btnKneelClick(Action *action) { if (_popup) return; // TODO: check for timeunits... check for FOV... BattleUnit *bu = _battleGame->getSelectedUnit(); if (bu) { if (bu->spendTimeUnits(bu->isKneeled()?8:4, _battleGame->getDebugMode())) { bu->kneel(!bu->isKneeled()); _map->cacheUnits(); updateSoldierInfo(bu); } } }
/** * calculateTrajectory. * @return true when a trajectory is possible. */ bool Projectile::calculateTrajectory(double accuracy) { Position originVoxel, targetVoxel; int direction; int dirYshift[8] = {1, 1, 8, 15, 15, 15, 8, 1 }; int dirXshift[8] = {8, 14, 15, 15, 8, 1, 1, 1 }; // large units : x2 originVoxel = Position(_origin.x*16, _origin.y*16, _origin.z*24); originVoxel.z += -_save->getTile(_origin)->getTerrainLevel(); BattleUnit *bu = _save->getTile(_origin)->getUnit(); originVoxel.z += bu->isKneeled()?bu->getUnit()->getKneelHeight():bu->getUnit()->getStandHeight(); originVoxel.z -= 3; if (originVoxel.z >= (_origin.z + 1)*24) { _origin.z++; } direction = bu->getDirection(); originVoxel.x += dirXshift[direction]; originVoxel.y += 15-dirYshift[direction]; // determine the target voxel. // aim at the center of the unit, the object, the walls or the floor (in that priority) // if there is no LOF to the center, try elsewhere (more outward). // Store this target voxel. Tile *tile = _save->getTile(_target); if (tile->getUnit() != 0) { if (_origin == _target) { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 8, _target.z*24); } else { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 8, _target.z*24 + tile->getUnit()->getUnit()->getStandHeight()/2); } } else if (tile->getMapData(O_OBJECT) != 0) { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 8, _target.z*24 + 10); } else if (tile->getMapData(O_NORTHWALL) != 0) { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 16, _target.z*24 + 10); } else if (tile->getMapData(O_WESTWALL) != 0) { targetVoxel = Position(_target.x*16, _target.y*16 + 8, _target.z*24 + 10); } else if (tile->getMapData(O_FLOOR) != 0) { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 8, _target.z*24); } else { return false; // no line of fire } // apply some accuracy modifiers (todo: calculate this) // This will results in a new target voxel applyAccuracy(originVoxel, &targetVoxel, accuracy); // finally do a line calculation and store this trajectory. _save->getTerrainModifier()->calculateLine(originVoxel, targetVoxel, true, &_trajectory, bu); return true; }
/** * applyAccuracy calculates the new target in voxel space, based on the given accuracy modifier. * @param origin Startposition of the trajectory. * @param target Endpoint of the trajectory. * @param accuracy Accuracy modifier. * @param targetTile Tile of target. Default = 0. */ void Projectile::applyAccuracy(const Position& origin, Position *target, double accuracy, bool keepRange, Tile *targetTile) { int xdiff = origin.x - target->x; int ydiff = origin.y - target->y; double realDistance = sqrt((double)(xdiff*xdiff)+(double)(ydiff*ydiff)); // maxRange is the maximum range a projectile shall ever travel in voxel space double maxRange = keepRange?realDistance:16*1000; // 1000 tiles maxRange = _action.type == BA_HIT?46:maxRange; // up to 2 tiles diagonally (as in the case of reaper v reaper) if (Options::getBool("battleRangeBasedAccuracy")) { double baseDeviation, accuracyPenalty; if (targetTile) { BattleUnit* targetUnit = targetTile->getUnit(); if (targetUnit && (targetUnit->getFaction() == FACTION_HOSTILE)) accuracyPenalty = 0.01 * targetTile->getShade(); // Shade can be from 0 to 15 else accuracyPenalty = 0.0; // Enemy units can see in the dark. // If unit is kneeled, then chance to hit them reduced on 5%. This is a compromise, because vertical deviation in 2 times less. if (targetUnit && targetUnit->isKneeled()) accuracyPenalty += 0.05; } else accuracyPenalty = 0.01 * _save->getGlobalShade(); // Shade can be from 0 (day) to 15 (night). baseDeviation = -0.15 + (_action.type == BA_AUTOSHOT? 0.28 : 0.26) / (accuracy - accuracyPenalty + 0.25); // 0.02 is the min angle deviation for best accuracy (+-3s = 0.02 radian). if (baseDeviation < 0.02) baseDeviation = 0.02; // the angle deviations are spread using a normal distribution for baseDeviation (+-3s with precision 99,7%) double dH = RNG::boxMuller(0.0, baseDeviation / 6.0); // horizontal miss in radian double dV = RNG::boxMuller(0.0, baseDeviation /(6.0 * 2)); double te = atan2(double(target->y - origin.y), double(target->x - origin.x)) + dH; double fi = atan2(double(target->z - origin.z), realDistance) + dV; double cos_fi = cos(fi); // It is a simple task - to hit in target width of 5-7 voxels. Good luck! target->x = (int)(origin.x + maxRange * cos(te) * cos_fi); target->y = (int)(origin.y + maxRange * sin(te) * cos_fi); target->z = (int)(origin.z + maxRange * sin(fi)); return; } // maxDeviation is the max angle deviation for accuracy 0% in degrees double maxDeviation = 2.5; // minDeviation is the min angle deviation for accuracy 100% in degrees double minDeviation = 0.4; double dRot, dTilt; double rotation, tilt; double baseDeviation = (maxDeviation - (maxDeviation * accuracy)) + minDeviation; // the angle deviations are spread using a normal distribution between 0 and baseDeviation // check if we hit if (RNG::generate(0.0, 1.0) < accuracy) { // we hit, so no deviation dRot = 0; dTilt = 0; } else { dRot = RNG::boxMuller(0, baseDeviation); dTilt = RNG::boxMuller(0, baseDeviation / 2.0); // tilt deviation is halved } rotation = atan2(double(target->y - origin.y), double(target->x - origin.x)) * 180 / M_PI; tilt = atan2(double(target->z - origin.z), sqrt(double(target->x - origin.x)*double(target->x - origin.x)+double(target->y - origin.y)*double(target->y - origin.y))) * 180 / M_PI; // add deviations rotation += dRot; tilt += dTilt; // calculate new target // this new target can be very far out of the map, but we don't care about that right now double cos_fi = cos(tilt * M_PI / 180.0); double sin_fi = sin(tilt * M_PI / 180.0); double cos_te = cos(rotation * M_PI / 180.0); double sin_te = sin(rotation * M_PI / 180.0); target->x = (int)(origin.x + maxRange * cos_te * cos_fi); target->y = (int)(origin.y + maxRange * sin_te * cos_fi); target->z = (int)(origin.z + maxRange * sin_fi); }