/*calculates how much space is remaining on the transporter - allows droids to take up different amount depending on their body size - currently all are set to one!*/ int calcRemainingCapacity(const DROID *psTransporter) { int capacity = TRANSPORTER_CAPACITY; DROID *psDroid, *psNext; // If it's dead then just return 0. if (isDead((BASE_OBJECT *)psTransporter)) { return 0; } for (psDroid = psTransporter->psGroup->psList; psDroid != nullptr && psDroid != psTransporter; psDroid = psNext) { psNext = psDroid->psGrpNext; const int space = transporterSpaceRequired(psDroid); ASSERT(space > 0, "Invalid space required for %s", objInfo(psDroid)); capacity -= space; } if (capacity < 0) { capacity = 0; } return capacity; }
// free up a feature with no visual effects bool removeFeature(FEATURE *psDel) { MESSAGE *psMessage; Vector3i pos; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); ASSERT_OR_RETURN(false, !psDel->died, "Feature already dead"); //remove from the map data StructureBounds b = getStructureBounds(psDel); for (int breadth = 0; breadth < b.size.y; ++breadth) { for (int width = 0; width < b.size.x; ++width) { if (tileOnMap(b.map.x + width, b.map.y + breadth)) { MAPTILE *psTile = mapTile(b.map.x + width, b.map.y + breadth); if (psTile->psObject == psDel) { psTile->psObject = NULL; auxClearBlocking(b.map.x + width, b.map.y + breadth, FEATURE_BLOCKED | AIR_BLOCKED); } } } } if (psDel->psStats->subType == FEAT_GEN_ARTE || psDel->psStats->subType == FEAT_OIL_DRUM) { pos.x = psDel->pos.x; pos.z = psDel->pos.y; pos.y = map_Height(pos.x, pos.z) + 30; addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_DISCOVERY, false, NULL, 0, gameTime - deltaGameTime + 1); if (psDel->psStats->subType == FEAT_GEN_ARTE) { scoreUpdateVar(WD_ARTEFACTS_FOUND); intRefreshScreen(); } } if (psDel->psStats->subType == FEAT_GEN_ARTE || psDel->psStats->subType == FEAT_OIL_RESOURCE) { for (unsigned player = 0; player < MAX_PLAYERS; ++player) { psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); while (psMessage) { removeMessage(psMessage, player); psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); } } } debug(LOG_DEATH, "Killing off feature %s id %d (%p)", objInfo(psDel), psDel->id, psDel); killFeature(psDel); return true; }
static bool baseObjDead(INTERP_VAL *psVal) { BASE_OBJECT *psObj = (BASE_OBJECT *)psVal->v.oval; if (psObj && isDead(psObj)) { debug(LOG_DEATH, "Removing %p (%s) from the wzscript system", psObj, objInfo(psObj)); psVal->v.oval = NULL; return true; } return false; }
static inline void destroyObject(OBJECT* list[], OBJECT* object) { ASSERT(object != NULL, "Invalid pointer"); // If the message to remove is the first one in the list then mark the next one as the first if (list[object->player] == object) { list[object->player] = list[object->player]->psNext; object->psNext = psDestroyedObj; psDestroyedObj = (BASE_OBJECT *)object; object->died = gameTime; scriptRemoveObject(object); return; } // Iterate through the list and find the item before the object to delete OBJECT *psPrev = NULL, *psCurr; for(psCurr = list[object->player]; (psCurr != object) && (psCurr != NULL); psCurr = psCurr->psNext) { psPrev = psCurr; } ASSERT(psCurr != NULL, "Object %s(%d) not found in list", objInfo(object), object->id); if (psCurr != NULL) { // Modify the "next" pointer of the previous item to // point to the "next" item of the item to delete. psPrev->psNext = psCurr->psNext; // Prepend the object to the destruction list object->psNext = psDestroyedObj; psDestroyedObj = object; // Set destruction time object->died = gameTime; } scriptRemoveObject(object); }
// free up a feature with no visual effects bool removeFeature(FEATURE *psDel) { int mapX, mapY, width, breadth, player; MESSAGE *psMessage; Vector3i pos; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); ASSERT_OR_RETURN(false, !psDel->died, "Feature already dead"); if(bMultiMessages && !ingame.localJoiningInProgress) { SendDestroyFeature(psDel); // inform other players of destruction return true; // Wait for our message before really destroying the feature. } //remove from the map data mapX = map_coord(psDel->pos.x) - psDel->psStats->baseWidth/2; mapY = map_coord(psDel->pos.y) - psDel->psStats->baseBreadth/2; for (width = 0; width < psDel->psStats->baseWidth; width++) { for (breadth = 0; breadth < psDel->psStats->baseBreadth; breadth++) { if (tileOnMap(mapX + width, mapY + breadth)) { MAPTILE *psTile = mapTile(mapX + width, mapY + breadth); if (psTile->psObject == (BASE_OBJECT *)psDel) { psTile->psObject = NULL; auxClearBlocking(mapX + width, mapY + breadth, FEATURE_BLOCKED | AIR_BLOCKED); } } } } if (psDel->psStats->subType == FEAT_GEN_ARTE || psDel->psStats->subType == FEAT_OIL_DRUM) { pos.x = psDel->pos.x; pos.z = psDel->pos.y; pos.y = map_Height(pos.x, pos.z) + 30; addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_DISCOVERY,false,NULL,0); if (psDel->psStats->subType == FEAT_GEN_ARTE) { scoreUpdateVar(WD_ARTEFACTS_FOUND); intRefreshScreen(); } } if (psDel->psStats->subType == FEAT_GEN_ARTE || psDel->psStats->subType == FEAT_OIL_RESOURCE) { for (player = 0; player < MAX_PLAYERS; player++) { psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); while (psMessage) { removeMessage(psMessage, player); psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); } } } debug(LOG_DEATH, "Killing off feature %s id %d (%p)", objInfo(psDel), psDel->id, psDel); killFeature(psDel); return true; }
// Add the droid order screen. // Returns true if the form was displayed ok. // //changed to a BASE_OBJECT to accomodate the factories - AB 21/04/99 bool intAddOrder(BASE_OBJECT *psObj) { bool Animate = true; SECONDARY_STATE State; UWORD OrdIndex; UWORD Height, NumDisplayedOrders; UWORD NumButs; UWORD NumJustifyButs, NumCombineButs, NumCombineBefore; bool bLastCombine, bHidden; DROID *Droid; STRUCTURE *psStructure; if (bInTutorial) { // No RMB orders in tutorial!! return (false); } // Is the form already up? if (widgGetFromID(psWScreen, IDORDER_FORM) != NULL) { intRemoveOrderNoAnim(); Animate = false; } // Is the stats window up? if (widgGetFromID(psWScreen, IDSTAT_FORM) != NULL) { intRemoveStatsNoAnim(); Animate = false; } if (psObj) { if (psObj->type == OBJ_DROID) { Droid = (DROID *)psObj; psStructure = NULL; } else if (psObj->type == OBJ_STRUCTURE) { Droid = NULL; psStructure = (STRUCTURE *)psObj; psSelectedFactory = psStructure; ASSERT_OR_RETURN(false, StructIsFactory(psSelectedFactory), "Trying to select a %s as a factory!", objInfo((BASE_OBJECT *)psSelectedFactory)); } else { ASSERT(false, "Invalid object type"); Droid = NULL; psStructure = NULL; } } else { Droid = NULL; psStructure = NULL; } setWidgetsStatus(true); AvailableOrders.clear(); SelectedDroids.clear(); // Selected droid is a command droid? if ((Droid != NULL) && (Droid->droidType == DROID_COMMAND)) { // displaying for a command droid - ignore any other droids SelectedDroids.push_back(Droid); } else if (psStructure != NULL) { AvailableOrders = buildStructureOrderList(psStructure); if (AvailableOrders.empty()) { return false; } } // Otherwise build a list of selected droids. else if (!BuildSelectedDroidList()) { // If no droids selected then see if we were given a specific droid. if (Droid != NULL) { // and put it in the list. SelectedDroids.push_back(Droid); } } // Build a list of orders available for the list of selected droids. - if a factory has not been selected if (psStructure == NULL) { AvailableOrders = buildDroidOrderList(); if (AvailableOrders.empty()) { // If no orders then return; return false; } } WIDGET *parent = psWScreen->psForm; /* Create the basic form */ IntFormAnimated *orderForm = new IntFormAnimated(parent, Animate); // Do not animate the opening, if the window was already open. orderForm->id = IDORDER_FORM; orderForm->setGeometry(ORDER_X, ORDER_Y, ORDER_WIDTH, ORDER_HEIGHT); // Add the close button. W_BUTINIT sButInit; sButInit.formID = IDORDER_FORM; sButInit.id = IDORDER_CLOSE; sButInit.x = ORDER_WIDTH - CLOSE_WIDTH; sButInit.y = 0; sButInit.width = CLOSE_WIDTH; sButInit.height = CLOSE_HEIGHT; sButInit.pTip = _("Close"); sButInit.pDisplay = intDisplayImageHilight; sButInit.UserData = PACKDWORD_TRI(0, IMAGE_CLOSEHILIGHT , IMAGE_CLOSE); if (!widgAddButton(psWScreen, &sButInit)) { return false; } sButInit = W_BUTINIT(); sButInit.formID = IDORDER_FORM; sButInit.id = IDORDER_CLOSE + 1; sButInit.pDisplay = intDisplayButtonHilight; sButInit.y = ORDER_BUTY; Height = 0; NumDisplayedOrders = 0; for (unsigned j = 0; j < AvailableOrders.size() && NumDisplayedOrders < MAX_DISPLAYABLE_ORDERS; ++j) { OrdIndex = AvailableOrders[j].OrderIndex; // Get current order state. State = GetSecondaryStates(OrderButtons[OrdIndex].Order); // Get number of buttons. NumButs = OrderButtons[OrdIndex].NumButs; // Set actual number of buttons. OrderButtons[OrdIndex].AcNumButs = NumButs; // Handle special case for factory -> command droid assignment buttons. switch (OrderButtons[OrdIndex].Class) { case ORDBUTCLASS_FACTORY: NumButs = countAssignableFactories((UBYTE)selectedPlayer, FACTORY_FLAG); break; case ORDBUTCLASS_CYBORGFACTORY: NumButs = countAssignableFactories((UBYTE)selectedPlayer, CYBORG_FLAG); break; case ORDBUTCLASS_VTOLFACTORY: NumButs = countAssignableFactories((UBYTE)selectedPlayer, VTOL_FLAG); break; default: break; } sButInit.id = OrderButtons[OrdIndex].ButBaseID; NumJustifyButs = NumButs; bLastCombine = false; switch (OrderButtons[OrdIndex].ButJustify & ORD_JUSTIFY_MASK) { case ORD_JUSTIFY_LEFT: sButInit.x = ORDER_BUTX; break; case ORD_JUSTIFY_RIGHT: sButInit.x = orderForm->width() - ORDER_BUTX - (NumJustifyButs * GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) + (NumJustifyButs - 1) * ORDER_BUTGAP); break; case ORD_JUSTIFY_CENTER: sButInit.x = (orderForm->width() - (NumJustifyButs * GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) + (NumJustifyButs - 1) * ORDER_BUTGAP)) / 2; break; case ORD_JUSTIFY_COMBINE: // see how many are on this line before the button NumCombineBefore = 0; for (unsigned i = 0; i < j; ++i) { if ((OrderButtons[AvailableOrders[i].OrderIndex].ButJustify & ORD_JUSTIFY_MASK) == ORD_JUSTIFY_COMBINE) { NumCombineBefore += 1; } } NumCombineButs = (UWORD)(NumCombineBefore + 1); // now see how many in total for (unsigned i = j + 1; i < AvailableOrders.size(); ++i) { if ((OrderButtons[AvailableOrders[i].OrderIndex].ButJustify & ORD_JUSTIFY_MASK) == ORD_JUSTIFY_COMBINE) { NumCombineButs += 1; } } // get position on line NumCombineButs = (UWORD)(NumCombineButs - (NumCombineBefore - (NumCombineBefore % ORD_MAX_COMBINE_BUTS))); if (NumCombineButs >= ORD_MAX_COMBINE_BUTS) { // the buttons will fill the line sButInit.x = (SWORD)(ORDER_BUTX + (GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) + ORDER_BUTGAP) * NumCombineBefore); } else { // center the buttons sButInit.x = orderForm->width() / 2 - (NumCombineButs * GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) + (NumCombineButs - 1) * ORDER_BUTGAP) / 2; sButInit.x = (SWORD)(sButInit.x + (GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) + ORDER_BUTGAP) * NumCombineBefore); } // see if need to start a new line of buttons if ((NumCombineBefore + 1) == (NumCombineButs % ORD_MAX_COMBINE_BUTS)) { bLastCombine = true; } break; } for (unsigned i = 0; i < OrderButtons[OrdIndex].AcNumButs; ++i) { sButInit.pTip = getDORDDescription(OrderButtons[OrdIndex].ButTips[i]); sButInit.width = (UWORD)GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[i]); sButInit.height = (UWORD)GetImageHeight(IntImages, OrderButtons[OrdIndex].ButImageID[i]); sButInit.UserData = PACKDWORD_TRI(OrderButtons[OrdIndex].ButGreyID[i], OrderButtons[OrdIndex].ButHilightID[i], OrderButtons[OrdIndex].ButImageID[i]); if (!widgAddButton(psWScreen, &sButInit)) { return false; } // Set the default state for the button. switch (OrderButtons[OrdIndex].ButType) { case ORD_BTYPE_RADIO: case ORD_BTYPE_BOOLEAN: if ((State & OrderButtons[OrdIndex].StateMask) == (UDWORD)OrderButtons[OrdIndex].States[i]) { widgSetButtonState(psWScreen, sButInit.id, WBUT_CLICKLOCK); } else { widgSetButtonState(psWScreen, sButInit.id, 0); } break; case ORD_BTYPE_BOOLEAN_DEPEND: if ((State & OrderButtons[OrdIndex].StateMask) == (UDWORD)OrderButtons[OrdIndex].States[i]) { widgSetButtonState(psWScreen, sButInit.id, WBUT_CLICKLOCK); } else { if (i == 0) { widgSetButtonState(psWScreen, sButInit.id, 0); } else { widgSetButtonState(psWScreen, sButInit.id, WBUT_DISABLE); } } break; case ORD_BTYPE_BOOLEAN_COMBINE: if (State & (UDWORD)OrderButtons[OrdIndex].States[i]) { widgSetButtonState(psWScreen, sButInit.id, WBUT_CLICKLOCK); } break; } // may not add a button if the factory doesn't exist bHidden = false; switch (OrderButtons[OrdIndex].Class) { case ORDBUTCLASS_FACTORY: if (!checkFactoryExists(selectedPlayer, FACTORY_FLAG, i)) { widgHide(psWScreen, sButInit.id); bHidden = true; } break; case ORDBUTCLASS_CYBORGFACTORY: if (!checkFactoryExists(selectedPlayer, CYBORG_FLAG, i)) { widgHide(psWScreen, sButInit.id); bHidden = true; } break; case ORDBUTCLASS_VTOLFACTORY: if (!checkFactoryExists(selectedPlayer, VTOL_FLAG, i)) { widgHide(psWScreen, sButInit.id); bHidden = true; } break; default: break; } if (!bHidden) { sButInit.x = (SWORD)(sButInit.x + sButInit.width + ORDER_BUTGAP); } sButInit.id++; } if (((OrderButtons[OrdIndex].ButJustify & ORD_JUSTIFY_MASK) != ORD_JUSTIFY_COMBINE) || bLastCombine) { sButInit.y = (SWORD)(sButInit.y + sButInit.height + ORDER_BUTGAP); Height = (UWORD)(Height + sButInit.height + ORDER_BUTGAP); } NumDisplayedOrders ++; } // Now we know how many orders there are we can resize the form accordingly. int newHeight = Height + CLOSE_HEIGHT + ORDER_BUTGAP; orderForm->setGeometry(orderForm->x(), ORDER_BOTTOMY - newHeight, orderForm->width(), newHeight); OrderUp = true; return true; }
/* Fire a weapon at something */ bool combFire(WEAPON *psWeap, BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, int weapon_slot) { WEAPON_STATS *psStats; UDWORD firePause; SDWORD longRange; int compIndex; CHECK_OBJECT(psAttacker); CHECK_OBJECT(psTarget); ASSERT(psWeap != NULL, "Invalid weapon pointer"); /* Watermelon:dont shoot if the weapon_slot of a vtol is empty */ if (psAttacker->type == OBJ_DROID && isVtolDroid(((DROID *)psAttacker))) { if (psWeap->usedAmmo >= getNumAttackRuns(((DROID *)psAttacker), weapon_slot)) { objTrace(psAttacker->id, "VTOL slot %d is empty", weapon_slot); return false; } } /* Get the stats for the weapon */ compIndex = psWeap->nStat; ASSERT_OR_RETURN( false , compIndex < numWeaponStats, "Invalid range referenced for numWeaponStats, %d > %d", compIndex, numWeaponStats); psStats = asWeaponStats + compIndex; // check valid weapon/prop combination if (!validTarget(psAttacker, psTarget, weapon_slot)) { return false; } unsigned fireTime = gameTime - deltaGameTime + 1; // Can fire earliest at the start of the tick. // See if reloadable weapon. if (psStats->reloadTime) { unsigned reloadTime = psWeap->lastFired + weaponReloadTime(psStats, psAttacker->player); if (psWeap->ammo == 0) // Out of ammo? { fireTime = std::max(fireTime, reloadTime); // Have to wait for weapon to reload before firing. if (gameTime < fireTime) { return false; } } if (reloadTime <= fireTime) { //reset the ammo level psWeap->ammo = psStats->numRounds; } } /* See when the weapon last fired to control it's rate of fire */ firePause = weaponFirePause(psStats, psAttacker->player); firePause = std::max(firePause, 1u); // Don't shoot infinitely many shots at once. fireTime = std::max(fireTime, psWeap->lastFired + firePause); if (gameTime < fireTime) { /* Too soon to fire again */ return false; } if (psTarget->visible[psAttacker->player] != UBYTE_MAX) { // Can't see it - can't hit it objTrace(psAttacker->id, "combFire(%u[%s]->%u): Object has no indirect sight of target", psAttacker->id, psStats->pName, psTarget->id); return false; } /* Check we can hit the target */ bool tall = (psAttacker->type == OBJ_DROID && isVtolDroid((DROID *)psAttacker)) || (psAttacker->type == OBJ_STRUCTURE && ((STRUCTURE *)psAttacker)->pStructureType->height > 1); if (proj_Direct(psStats) && !lineOfFire(psAttacker, psTarget, weapon_slot, tall)) { // Can't see the target - can't hit it with direct fire objTrace(psAttacker->id, "combFire(%u[%s]->%u): No direct line of sight to target", psAttacker->id, objInfo(psAttacker), psTarget->id); return false; } Vector3i deltaPos = psTarget->pos - psAttacker->pos; // if the turret doesn't turn, check if the attacker is in alignment with the target if (psAttacker->type == OBJ_DROID && !psStats->rotate) { uint16_t targetDir = iAtan2(removeZ(deltaPos)); int dirDiff = abs(angleDelta(targetDir - psAttacker->rot.direction)); if (dirDiff > FIXED_TURRET_DIR) { return false; } } /* Now see if the target is in range - also check not too near */ int dist = iHypot(removeZ(deltaPos)); longRange = proj_GetLongRange(psStats); int min_angle = 0; // Calculate angle for indirect shots if (!proj_Direct(psStats) && dist > 0) { min_angle = arcOfFire(psAttacker,psTarget,weapon_slot,true); // prevent extremely steep shots min_angle = std::min(min_angle, DEG(PROJ_ULTIMATE_PITCH)); // adjust maximum range of unit if forced to shoot very steep if (min_angle > DEG(PROJ_MAX_PITCH)) { //do not allow increase of max range though if (iSin(2*min_angle) < iSin(2*DEG(PROJ_MAX_PITCH))) // If PROJ_MAX_PITCH == 45, then always iSin(2*min_angle) <= iSin(2*DEG(PROJ_MAX_PITCH)), and the test is redundant. { longRange = longRange * iSin(2*min_angle) / iSin(2*DEG(PROJ_MAX_PITCH)); } } } int baseHitChance = 0; if (dist <= longRange && dist >= psStats->minRange) { // get weapon chance to hit in the long range baseHitChance = weaponLongHit(psStats,psAttacker->player); // adapt for height adjusted artillery shots if (min_angle > DEG(PROJ_MAX_PITCH)) { baseHitChance = baseHitChance * iCos(min_angle) / iCos(DEG(PROJ_MAX_PITCH)); } } else { /* Out of range */ objTrace(psAttacker->id, "combFire(%u[%s]->%u): Out of range", psAttacker->id, psStats->pName, psTarget->id); return false; } // apply experience accuracy modifiers to the base //hit chance, not to the final hit chance int resultHitChance = baseHitChance; // add the attacker's experience if (psAttacker->type == OBJ_DROID) { SDWORD level = getDroidEffectiveLevel((DROID *) psAttacker); // increase total accuracy by EXP_ACCURACY_BONUS % for each experience level resultHitChance += EXP_ACCURACY_BONUS * level * baseHitChance / 100; } // subtract the defender's experience if (psTarget->type == OBJ_DROID) { SDWORD level = getDroidEffectiveLevel((DROID *) psTarget); // decrease weapon accuracy by EXP_ACCURACY_BONUS % for each experience level resultHitChance -= EXP_ACCURACY_BONUS * level * baseHitChance / 100; } // fire while moving modifiers if (psAttacker->type == OBJ_DROID && ((DROID *)psAttacker)->sMove.Status != MOVEINACTIVE) { switch (psStats->fireOnMove) { case FOM_NO: // Can't fire while moving return false; break; case FOM_PARTIAL: resultHitChance = FOM_PARTIAL_ACCURACY_PENALTY * resultHitChance / 100; break; case FOM_YES: // can fire while moving break; } } /* -------!!! From that point we are sure that we are firing !!!------- */ // Add a random delay to the next shot. // TODO Add deltaFireTime to the time it takes to fire next. If just adding to psWeap->lastFired, it might put it in the future, causing assertions. And if not sometimes putting it in the future, the fire rate would be lower than advertised. //int fireJitter = firePause/100; // ±1% variation in fire rate. //int deltaFireTime = gameRand(fireJitter*2 + 1) - fireJitter; /* note when the weapon fired */ psWeap->lastFired = fireTime; /* reduce ammo if salvo */ if (psStats->reloadTime) { psWeap->ammo--; } // increment the shots counter psWeap->shotsFired++; // predicted X,Y offset per sec Vector3i predict = psTarget->pos; //Watermelon:Target prediction if (isDroid(psTarget) && castDroid(psTarget)->sMove.bumpTime == 0) { DROID *psDroid = castDroid(psTarget); int32_t flightTime; if (proj_Direct(psStats) || dist <= psStats->minRange) { flightTime = dist * GAME_TICKS_PER_SEC / psStats->flightSpeed; } else { int32_t vXY, vZ; // Unused, we just want the flight time. flightTime = projCalcIndirectVelocities(dist, deltaPos.z, psStats->flightSpeed, &vXY, &vZ, min_angle); } if (psTarget->lastHitWeapon == WSC_EMP) { int playerEmpTime = getEmpDisableTime(psTarget->player); int empTime = playerEmpTime - (gameTime - psTarget->timeLastHit); CLIP(empTime, 0, playerEmpTime); if (empTime >= playerEmpTime * 9/10) { flightTime = 0; /* Just hit. Assume they'll get hit again */ } else { flightTime = MAX(0, flightTime - empTime); } } predict += Vector3i(iSinCosR(psDroid->sMove.moveDir, psDroid->sMove.speed*flightTime / GAME_TICKS_PER_SEC), 0); if (!isFlying(psDroid)) { predict.z = map_Height(removeZ(predict)); // Predict that the object will be on the ground. } } /* Fire off the bullet to the miss location. The miss is only visible if the player owns the target. (Why? - Per) */ // What bVisible really does is to make the projectile audible even if it misses you. Since the target is NULL, proj_SendProjectile can't check if it was fired at you. bool bVisibleAnyway = psTarget->player == selectedPlayer; // see if we were lucky to hit the target bool isHit = gameRand(100) <= resultHitChance; if (isHit) { /* Kerrrbaaang !!!!! a hit */ objTrace(psAttacker->id, "combFire: [%s]->%u: resultHitChance=%d, visibility=%d", psStats->pName, psTarget->id, resultHitChance, (int)psTarget->visible[psAttacker->player]); syncDebug("hit=(%d,%d,%d)", predict.x, predict.y, predict.z); } else /* Deal with a missed shot */ { const int minOffset = 5; int missDist = 2 * (100 - resultHitChance) + minOffset; Vector3i miss = Vector3i(iSinCosR(gameRand(DEG(360)), missDist), 0); predict += miss; psTarget = NULL; // Missed the target, so don't expect to hit it. objTrace(psAttacker->id, "combFire: Missed shot by (%4d,%4d)", miss.x, miss.y); syncDebug("miss=(%d,%d,%d)", predict.x, predict.y, predict.z); } // Make sure we don't pass any negative or out of bounds numbers to proj_SendProjectile CLIP(predict.x, 0, world_coord(mapWidth - 1)); CLIP(predict.y, 0, world_coord(mapHeight - 1)); proj_SendProjectileAngled(psWeap, psAttacker, psAttacker->player, predict, psTarget, bVisibleAnyway, weapon_slot, min_angle, fireTime); return true; }
/* Build a droid template in the specified factory */ static QScriptValue js_buildDroid(QScriptContext *context, QScriptEngine *) { QScriptValue structVal = context->argument(1); int id = structVal.property("id").toInt32(); int player = structVal.property("player").toInt32(); QScriptValue templName = context->argument(0); DROID_TEMPLATE *psTemplate = getTemplateFromTranslatedNameNoPlayer(templName.toString().toUtf8().constData()); STRUCTURE *psStruct = IdToStruct(id, player); SCRIPT_ASSERT(context, psStruct != NULL, "No factory object found for id %d, player %d", id, player); SCRIPT_ASSERT(context, psTemplate != NULL, "No template object found for %s sent to %s", templName.toString().toUtf8().constData(), objInfo(psStruct)); SCRIPT_ASSERT(context, (psStruct->pStructureType->type == REF_FACTORY || psStruct->pStructureType->type == REF_CYBORG_FACTORY || psStruct->pStructureType->type == REF_VTOL_FACTORY), "Structure %s is not a factory", objInfo(psStruct)); SCRIPT_ASSERT(context, validTemplateForFactory(psTemplate, psStruct), "Invalid template - %s for factory - %s", psTemplate->aName, psStruct->pStructureType->pName); return QScriptValue(structSetManufacture(psStruct, psTemplate, ModeQueue)); }
// Find the best nearest target for a droid. // If extraRange is higher than zero, then this is the range it accepts for movement to target. // Returns integer representing target priority, -1 if failed int aiBestNearestTarget(DROID *psDroid, BASE_OBJECT **ppsObj, int weapon_slot, int extraRange) { SDWORD bestMod = 0, newMod, failure = -1; BASE_OBJECT *psTarget = NULL, *bestTarget = NULL, *tempTarget; bool electronic = false; STRUCTURE *targetStructure; WEAPON_EFFECT weaponEffect; TARGET_ORIGIN tmpOrigin = ORIGIN_UNKNOWN; //don't bother looking if empty vtol droid if (vtolEmpty(psDroid)) { return failure; } /* Return if have no weapons */ // The ai orders a non-combat droid to patrol = crash without it... if ((psDroid->asWeaps[0].nStat == 0 || psDroid->numWeaps == 0) && psDroid->droidType != DROID_SENSOR) { return failure; } // Check if we have a CB target to begin with if (!proj_Direct(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat)) { WEAPON_STATS *psWStats = psDroid->asWeaps[weapon_slot].nStat + asWeaponStats; bestTarget = aiSearchSensorTargets((BASE_OBJECT *)psDroid, weapon_slot, psWStats, &tmpOrigin); bestMod = targetAttackWeight(bestTarget, (BASE_OBJECT *)psDroid, weapon_slot); } weaponEffect = (asWeaponStats + psDroid->asWeaps[weapon_slot].nStat)->weaponEffect; electronic = electronicDroid(psDroid); // Range was previously 9*TILE_UNITS. Increasing this doesn't seem to help much, though. Not sure why. int droidRange = std::min(aiDroidRange(psDroid, weapon_slot) + extraRange, objSensorRange(psDroid) + 6 * TILE_UNITS); static GridList gridList; // static to avoid allocations. gridList = gridStartIterate(psDroid->pos.x, psDroid->pos.y, droidRange); for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi) { BASE_OBJECT *friendlyObj = NULL; BASE_OBJECT *targetInQuestion = *gi; /* This is a friendly unit, check if we can reuse its target */ if (aiCheckAlliances(targetInQuestion->player, psDroid->player)) { friendlyObj = targetInQuestion; targetInQuestion = NULL; /* Can we see what it is doing? */ if (friendlyObj->visible[psDroid->player] == UBYTE_MAX) { if (friendlyObj->type == OBJ_DROID) { DROID *friendlyDroid = (DROID *)friendlyObj; /* See if friendly droid has a target */ tempTarget = friendlyDroid->psActionTarget[0]; if (tempTarget && !tempTarget->died) { //make sure a weapon droid is targeting it if (friendlyDroid->numWeaps > 0) { // make sure this target wasn't assigned explicitly to this droid if (friendlyDroid->order.type != DORDER_ATTACK) { targetInQuestion = tempTarget; //consider this target } } } } else if (friendlyObj->type == OBJ_STRUCTURE) { tempTarget = ((STRUCTURE *)friendlyObj)->psTarget[0]; if (tempTarget && !tempTarget->died) { targetInQuestion = tempTarget; } } } } if (targetInQuestion != NULL && targetInQuestion != psDroid // in case friendly unit had me as target && (targetInQuestion->type == OBJ_DROID || targetInQuestion->type == OBJ_STRUCTURE || targetInQuestion->type == OBJ_FEATURE) && targetInQuestion->visible[psDroid->player] == UBYTE_MAX && !aiCheckAlliances(targetInQuestion->player, psDroid->player) && validTarget(psDroid, targetInQuestion, weapon_slot) && objPosDiffSq(psDroid, targetInQuestion) < droidRange * droidRange) { if (targetInQuestion->type == OBJ_DROID) { // in multiPlayer - don't attack Transporters with EW if (bMultiPlayer) { // if not electronic then valid target if (!electronic || (electronic && !isTransporter((DROID *)targetInQuestion))) { //only a valid target if NOT a transporter psTarget = targetInQuestion; } } else { psTarget = targetInQuestion; } } else if (targetInQuestion->type == OBJ_STRUCTURE) { STRUCTURE *psStruct = (STRUCTURE *)targetInQuestion; if (electronic) { /* don't want to target structures with resistance of zero if using electronic warfare */ if (validStructResistance((STRUCTURE *)targetInQuestion)) { psTarget = targetInQuestion; } } else if (psStruct->asWeaps[0].nStat > 0) { // structure with weapons - go for this psTarget = targetInQuestion; } else if ((psStruct->pStructureType->type != REF_WALL && psStruct->pStructureType->type != REF_WALLCORNER) || driveModeActive() || (bMultiPlayer && !isHumanPlayer(psDroid->player))) { psTarget = targetInQuestion; } } else if (targetInQuestion->type == OBJ_FEATURE && psDroid->lastFrustratedTime > 0 && gameTime - psDroid->lastFrustratedTime < FRUSTRATED_TIME && ((FEATURE *)targetInQuestion)->psStats->damageable && psDroid->player != scavengerPlayer()) // hack to avoid scavs blowing up their nice feature walls { psTarget = targetInQuestion; objTrace(psDroid->id, "considering shooting at %s in frustration", objInfo(targetInQuestion)); } /* Check if our weapon is most effective against this object */ if (psTarget != NULL && psTarget == targetInQuestion) //was assigned? { newMod = targetAttackWeight(psTarget, (BASE_OBJECT *)psDroid, weapon_slot); /* Remember this one if it's our best target so far */ if (newMod >= 0 && (newMod > bestMod || bestTarget == NULL)) { bestMod = newMod; tmpOrigin = ORIGIN_ALLY; bestTarget = psTarget; } } } } if (bestTarget) { ASSERT(!bestTarget->died, "AI gave us a target that is already dead."); targetStructure = visGetBlockingWall((BASE_OBJECT *)psDroid, bestTarget); /* See if target is blocked by a wall; only affects direct weapons */ if (proj_Direct(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat) && targetStructure) { //are we any good against walls? if (asStructStrengthModifier[weaponEffect][targetStructure->pStructureType->strength] >= 100) //can attack atleast with default strength { bestTarget = (BASE_OBJECT *)targetStructure; //attack wall } } *ppsObj = bestTarget; return bestMod; } return failure; }
static inline void addObjectToFuncList(OBJECT *list[], OBJECT *object, int player) { ASSERT(object != NULL, "Invalid pointer"); ASSERT_OR_RETURN(, static_cast<OBJECT *>(object->psNextFunc) == NULL, "%s(%p) is already in a function list!", objInfo(object), object); // Prepend the object to the top of the list object->psNextFunc = list[player]; list[player] = object; }
/*** qytInfo - Return the capabilities of the object. ***/ int qytInfo(void* inf_v, pObjectInfo info) { pQytData inf = QYT(inf_v); pObjectInfo ll_info; pStructInf find_inf; int n_groups = 0; int is_recurse = 0; int is_leaf = 0; int i; pExpression exp; pParamObjects objlist; char* expstr; if (inf->LLObj) { /** Get basic character of object from underlying object **/ ll_info = objInfo(inf->LLObj); if (ll_info) memcpy(info, ll_info, sizeof(ObjectInfo)); /** Now add what we know, structurally, from the querytree **/ for(i=0;i<inf->NodeData->nSubInf;i++) { find_inf = inf->NodeData->SubInf[i]; if (stStructType(find_inf) == ST_T_SUBGROUP) n_groups++; else if (!strcmp(find_inf->Name,"recurse")) is_recurse = 1; else if (!strcmp(find_inf->Name,"known_leaf")) { objlist = expCreateParamList(); if (objlist) { expstr = NULL; stAttrValue(find_inf, NULL, &expstr, 0); if (expstr) { exp = (pExpression)expCompileExpression(expstr, objlist, MLX_F_ICASE | MLX_F_FILENAMES, 0); if (exp) { expAddParamToList(objlist,"",inf->LLObj,EXPR_O_CURRENT); if (expEvalTree(exp, objlist) >= 0 && exp->Integer != 0 && !(exp->Flags & EXPR_F_NULL)) is_leaf = 1; expFreeExpression(exp); } } expFreeParamList(objlist); } } } if ((is_recurse || n_groups > 0) && !is_leaf) { info->Flags &= ~( OBJ_INFO_F_NO_SUBOBJ | OBJ_INFO_F_CANT_HAVE_SUBOBJ ); info->Flags |= ( OBJ_INFO_F_CAN_HAVE_SUBOBJ ); } if (inf->Flags & QYT_F_FLEAF) info->Flags |= OBJ_INFO_F_FORCED_LEAF; return 0; } return -1; }
// NOTICE: the packet is already set-up for decoding via recvGift() static void recvGiftStruct(uint8_t from, uint8_t to, uint32_t structID) { STRUCTURE *psStruct = IdToStruct(structID, from); if (psStruct) { syncDebugStructure(psStruct, '<'); giftSingleStructure(psStruct, to, false); syncDebugStructure(psStruct, '>'); if (to == selectedPlayer) { CONPRINTF(ConsoleString, (ConsoleString, _("%s Gives you a %s"), getPlayerName(from), objInfo(psStruct))); } } else { debug(LOG_ERROR, "Bad structure id %u, from %u to %u", structID, from, to); } }