/** * @brief Updates spin animations * @param cur float value to update * @param dest float the final desired speed (NOT the final angle!) * @param speed float is updated if it is not equal to dest * @param divisor int is the deltatime, it is not added before the call because speed may have to be updated * @return true if the desired speed is 0 and it is reached, false otherwise */ bool CUnitScript::DoSpin(float &cur, float dest, float &speed, float accel, int divisor) { const float delta = dest - speed; // Check if we are not at the final speed and // make sure we dont go past desired speed if (math::fabsf(delta) <= accel) { speed = dest; if (speed == 0.0f) return true; } else { if (delta > 0.0f) { // accelerations are defined in speed/frame (at GAME_SPEED fps) speed += accel * (float(GAME_SPEED) / divisor); } else { speed -= accel * (float(GAME_SPEED) / divisor); } } cur += (speed / divisor); ClampRad(&cur); return false; }
/** * @brief Updates turn animations * @param cur float value to update * @param dest float final value * @param speed float max increment per tick * @return returns true if destination was reached, false otherwise */ bool CUnitScript::TurnToward(float &cur, float dest, float speed) { float delta = dest - cur; // clamp: -pi .. 0 .. +pi (remainder(x,TWOPI) would do the same but is slower due to streflop) if (delta > PI) { delta -= TWOPI; } else if (delta<=-PI) { delta += TWOPI; } if (math::fabsf(delta) <= speed) { cur = dest; return true; } if (delta > 0.0f) { cur += speed; } else { cur -= speed; } ClampRad(&cur); return false; }
/** * @brief Updates spin animations * @param cur float value to update * @param dest float the final desired speed (NOT the final angle!) * @param speed float is updated if it is not equal to dest * @param divisor int is the deltatime, it is not added before the call because speed may have to be updated * @return true if the desired speed is 0 and it is reached, false otherwise */ bool CUnitScript::DoSpin(float &cur, float dest, float &speed, float accel, int divisor) { const float delta = dest - speed; // Check if we are not at the final speed and // make sure we dont go past desired speed if (streflop::fabsf(delta) <= accel) { speed = dest; if (speed == 0.0f) return true; } else { if (delta > 0.0f) { //TA obviously defines accelerations in speed/frame (at 30 fps) speed += accel * (30.0f / divisor); } else { speed -= accel * (30.0f / divisor); } } cur += (speed / divisor); ClampRad(&cur); return false; }
//Overwrites old information. This means that threads blocking on turn completion //will now wait for this new turn instead. Not sure if this is the expected behaviour //Other option would be to kill them. Or perhaps unblock them. void CUnitScript::AddAnim(AnimType type, int piece, int axis, float speed, float dest, float accel, bool interpolated) { if (!PieceExists(piece)) { ShowScriptError("Invalid piecenumber"); return; } float destf; if (type == AMove) { destf = pieces[piece]->original->offset[axis] + dest; } else { destf = dest; if (type == ATurn) { ClampRad(&destf); } } struct AnimInfo *ai; //Turns override spins.. Not sure about the other way around? If so the system should probably be redesigned //to only have two types of anims.. turns and moves, with spin as a bool //todo: optimize, atm RemoveAnim and FindAnim search twice through all anims if (type == ATurn) RemoveAnim(ASpin, piece, axis); if (type == ASpin) RemoveAnim(ATurn, piece, axis); ai = FindAnim(type, piece, axis); if (!ai) { // If we were not animating before, inform the engine of this so it can schedule us if (anims.empty()) { GUnitScriptEngine.AddInstance(this); } ai = new struct AnimInfo; ai->type = type; ai->piece = piece; ai->axis = axis; anims.push_back(ai); } ai->dest = destf; ai->speed = speed; ai->accel = accel; ai->interpolated = interpolated; }
void CUnitScript::TurnSmooth(int piece, int axis, float destination, int delta, int deltaTime) { if (!PieceExists(piece)) { ShowScriptError("Invalid piecenumber"); return; } AnimInfo *ai = FindAnim(ATurn, piece, axis); if (ai) { if (!ai->interpolated) { TurnNow(piece, axis, destination); return; } } // not sure the ClampRad() call is necessary here float cur = ClampRad(pieces[piece]->rot[axis]); float dist = streflop::fabsf(destination - cur); int timeFactor = (1000 * 1000) / (deltaTime * deltaTime); float speed = (dist * timeFactor) / delta; Turn(piece, axis, speed, destination, true); }
//Overwrites old information. This means that threads blocking on turn completion //will now wait for this new turn instead. Not sure if this is the expected behaviour //Other option would be to kill them. Or perhaps unblock them. void CUnitScript::AddAnim(AnimType type, int piece, int axis, float speed, float dest, float accel) { if (!PieceExists(piece)) { ShowScriptError("Invalid piecenumber"); return; } float destf = 0.0f; if (type == AMove) { destf = pieces[piece]->original->offset[axis] + dest; } else { destf = dest; if (type == ATurn) { ClampRad(&destf); } } std::list<AnimInfo*>::iterator animInfoIt; AnimInfo* ai = NULL; AnimType overrideType = ANone; // first find an animation of a type we override // Turns override spins.. Not sure about the other way around? If so // the system should probably be redesigned to only have two types of // anims (turns and moves), with spin as a bool switch (type) { case ATurn: { overrideType = ASpin; animInfoIt = FindAnim(overrideType, piece, axis); } break; case ASpin: { overrideType = ATurn; animInfoIt = FindAnim(overrideType, piece, axis); } break; case AMove: { // ensure we never remove an animation of this type overrideType = AMove; animInfoIt = anims[overrideType].end(); } break; default: { } break; } if (animInfoIt != anims[overrideType].end()) RemoveAnim(overrideType, animInfoIt); // now find an animation of our own type animInfoIt = FindAnim(type, piece, axis); if (animInfoIt == anims[type].end()) { // If we were not animating before, inform the engine of this so it can schedule us // FIXME: this could be done in a cleaner way if (!HaveAnimations()) { GUnitScriptEngine.AddInstance(this); } ai = new AnimInfo(); ai->type = type; ai->piece = piece; ai->axis = axis; anims[type].push_back(ai); } else { ai = *animInfoIt; } ai->dest = destf; ai->speed = speed; ai->accel = accel; ai->done = false; }
void CWeapon::Update() { if (hasCloseTarget) { int weaponPiece = -1; bool weaponAimed = (useWeaponPosForAim == 0); // if we couldn't get a line of fire from the // muzzle, try if we can get it from the aim // piece if (!weaponAimed) { weaponPiece = owner->script->QueryWeapon(weaponNum); } else { weaponPiece = owner->script->AimFromWeapon(weaponNum); } relWeaponMuzzlePos = owner->script->GetPiecePos(weaponPiece); if (!weaponAimed) { weaponPiece = owner->script->AimFromWeapon(weaponNum); } relWeaponPos = owner->script->GetPiecePos(weaponPiece); } if (targetType == Target_Unit) { if (lastErrorVectorUpdate < gs->frameNum - UNIT_SLOWUPDATE_RATE) { float3 newErrorVector(gs->randVector()); errorVectorAdd = (newErrorVector - errorVector) * (1.0f / UNIT_SLOWUPDATE_RATE); lastErrorVectorUpdate = gs->frameNum; } errorVector += errorVectorAdd; if (predict > 50000) { // to prevent runaway prediction (happens sometimes when a missile // is moving *away* from its target), we may need to disable missiles // in case they fly around too long predict = 50000; } float3 lead = targetUnit->speed * (weaponDef->predictBoost+predictSpeedMod * (1.0f - weaponDef->predictBoost)) * predict; if (weaponDef->leadLimit >= 0.0f && lead.SqLength() > Square(weaponDef->leadLimit + weaponDef->leadBonus * owner->experience)) { lead *= (weaponDef->leadLimit + weaponDef->leadBonus*owner->experience) / (lead.Length() + 0.01f); } targetPos = helper->GetUnitErrorPos(targetUnit, owner->allyteam) + lead + errorVector * (weaponDef->targetMoveError * GAME_SPEED * targetUnit->speed.Length() * (1.0f - owner->limExperience)); const float appHeight = ground->GetApproximateHeight(targetPos.x, targetPos.z) + 2.0f; if (targetPos.y < appHeight) targetPos.y = appHeight; if (!weaponDef->waterweapon && targetPos.y < 1.0f) targetPos.y = 1.0f; } if (weaponDef->interceptor) { CheckIntercept(); } if (targetType != Target_None) { if (onlyForward) { float3 goaldir = (targetPos - owner->pos).Normalize(); angleGood = (owner->frontdir.dot(goaldir) > maxAngleDif); } else if (lastRequestedDir.dot(wantedDir) < maxAngleDif || (lastRequest + 15 < gs->frameNum)) { angleGood = false; lastRequestedDir = wantedDir; lastRequest = gs->frameNum; const float heading = GetHeadingFromVectorF(wantedDir.x, wantedDir.z); const float pitch = math::asin(Clamp(wantedDir.dot(owner->updir), -1.0f, 1.0f)); // for COB, this sets anglegood to return value of aim script when it finished, // for Lua, there exists a callout to set the anglegood member. // FIXME: convert CSolidObject::heading to radians too. owner->script->AimWeapon(weaponNum, ClampRad(heading - owner->heading * TAANG2RAD), pitch); } } if(weaponDef->stockpile && numStockpileQued){ float p=1.0f/stockpileTime; if(teamHandler->Team(owner->team)->metal>=metalFireCost*p && teamHandler->Team(owner->team)->energy>=energyFireCost*p){ owner->UseEnergy(energyFireCost*p); owner->UseMetal(metalFireCost*p); buildPercent+=p; } else { // update the energy and metal required counts teamHandler->Team(owner->team)->energyPull += energyFireCost*p; teamHandler->Team(owner->team)->metalPull += metalFireCost*p; } if(buildPercent>=1){ const int oldCount = numStockpiled; buildPercent=0; numStockpileQued--; numStockpiled++; owner->commandAI->StockpileChanged(this); eventHandler.StockpileChanged(owner, this, oldCount); } } if ((salvoLeft == 0) && (owner->fpsControlPlayer == NULL || owner->fpsControlPlayer->fpsController.mouse1 || owner->fpsControlPlayer->fpsController.mouse2) && (targetType != Target_None) && angleGood && subClassReady && (reloadStatus <= gs->frameNum) && (!weaponDef->stockpile || numStockpiled) && (weaponDef->fireSubmersed || (weaponMuzzlePos.y > 0)) && ((((owner->unitDef->maxFuel == 0) || (owner->currentFuel > 0) || (fuelUsage == 0)) && !isBeingServicedOnPad(owner))) ) { if ((weaponDef->stockpile || (teamHandler->Team(owner->team)->metal >= metalFireCost && teamHandler->Team(owner->team)->energy >= energyFireCost))) { const int piece = owner->script->QueryWeapon(weaponNum); owner->script->GetEmitDirPos(piece, relWeaponMuzzlePos, weaponDir); weaponMuzzlePos = owner->pos + owner->frontdir * relWeaponMuzzlePos.z + owner->updir * relWeaponMuzzlePos.y + owner->rightdir * relWeaponMuzzlePos.x; useWeaponPosForAim = reloadTime / 16 + 8; weaponDir = owner->frontdir * weaponDir.z + owner->updir * weaponDir.y + owner->rightdir * weaponDir.x; weaponDir.SafeNormalize(); if (TryTarget(targetPos, haveUserTarget, targetUnit) && !CobBlockShot(targetUnit)) { if (weaponDef->stockpile) { const int oldCount = numStockpiled; numStockpiled--; owner->commandAI->StockpileChanged(this); eventHandler.StockpileChanged(owner, this, oldCount); } else { owner->UseEnergy(energyFireCost); owner->UseMetal(metalFireCost); owner->currentFuel = std::max(0.0f, owner->currentFuel - fuelUsage); } reloadStatus = gs->frameNum + (int)(reloadTime / owner->reloadSpeed); salvoLeft = salvoSize; nextSalvo = gs->frameNum; salvoError = gs->randVector() * (owner->isMoving? weaponDef->movingAccuracy: accuracy); if (targetType == Target_Pos || (targetType == Target_Unit && !(targetUnit->losStatus[owner->allyteam] & LOS_INLOS))) { // area firing stuff is too effective at radar firing... salvoError *= 1.3f; } owner->lastMuzzleFlameSize = muzzleFlareSize; owner->lastMuzzleFlameDir = wantedDir; owner->script->FireWeapon(weaponNum); } } else { // FIXME -- never reached? if (TryTarget(targetPos, haveUserTarget, targetUnit) && !weaponDef->stockpile) { // update the energy and metal required counts const int minPeriod = std::max(1, (int)(reloadTime / owner->reloadSpeed)); const float averageFactor = 1.0f / (float)minPeriod; teamHandler->Team(owner->team)->energyPull += averageFactor * energyFireCost; teamHandler->Team(owner->team)->metalPull += averageFactor * metalFireCost; } } } if (salvoLeft && nextSalvo <= gs->frameNum) { salvoLeft--; nextSalvo = gs->frameNum + salvoDelay; owner->lastFireWeapon = gs->frameNum; int projectiles = projectilesPerShot; while (projectiles > 0) { --projectiles; // add to the commandShotCount if this is the last salvo, // and it is being directed towards the current target // (helps when deciding if a queued ground attack order has been completed) if (((salvoLeft == 0) && (owner->commandShotCount >= 0) && ((targetType == Target_Pos) && (targetPos == owner->userAttackPos))) || ((targetType == Target_Unit) && (targetUnit == owner->userTarget))) { owner->commandShotCount++; } owner->script->Shot(weaponNum); int piece = owner->script->AimFromWeapon(weaponNum); relWeaponPos = owner->script->GetPiecePos(piece); piece = owner->script->/*AimFromWeapon*/QueryWeapon(weaponNum); owner->script->GetEmitDirPos(piece, relWeaponMuzzlePos, weaponDir); weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x; weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x; weaponDir = owner->frontdir * weaponDir.z + owner->updir * weaponDir.y + owner->rightdir * weaponDir.x; weaponDir.SafeNormalize(); if (owner->unitDef->decloakOnFire && (owner->scriptCloak <= 2)) { if (owner->isCloaked) { owner->isCloaked = false; eventHandler.UnitDecloaked(owner); } owner->curCloakTimeout = gs->frameNum + owner->cloakTimeout; } Fire(); } //Rock the unit in the direction of the fireing if (owner->script->HasRockUnit()) { float3 rockDir = wantedDir; rockDir.y = 0; rockDir = -rockDir.Normalize(); owner->script->RockUnit(rockDir); } owner->commandAI->WeaponFired(this); if(salvoLeft==0){ owner->script->EndBurst(weaponNum); } #ifdef TRACE_SYNC tracefile << "Weapon fire: "; tracefile << weaponPos.x << " " << weaponPos.y << " " << weaponPos.z << " " << targetPos.x << " " << targetPos.y << " " << targetPos.z << "\n"; #endif } }