/* Give a group of droids an order */ void orderGroupObj(DROID_GROUP *psGroup, DROID_ORDER order, BASE_OBJECT *psObj) { DROID *psCurr; ASSERT((PTRVALID(psGroup, sizeof(DROID_GROUP)), "orderGroupObj: invalid droid group")); if(bMultiPlayer) { SendGroupOrderGroup(psGroup,order,0,0,psObj); bMultiPlayer = FALSE; for(psCurr = psGroup->psList; psCurr; psCurr = psCurr->psGrpNext) { orderDroidObj(psCurr, order, psObj); } bMultiPlayer = TRUE; } else { for(psCurr = psGroup->psList; psCurr; psCurr = psCurr->psGrpNext) { orderDroidObj(psCurr, order, psObj); } } }
/* Make droid/structure look for a better target */ static BOOL updateAttackTarget(BASE_OBJECT * psAttacker, SDWORD weapon_slot) { BASE_OBJECT *psBetterTarget=NULL; UWORD tmpOrigin = ORIGIN_UNKNOWN; if(aiChooseTarget(psAttacker, &psBetterTarget, weapon_slot, true, &tmpOrigin)) //update target { if(psAttacker->type == OBJ_DROID) { DROID *psDroid = (DROID *)psAttacker; if( (orderState(psDroid, DORDER_NONE) || orderState(psDroid, DORDER_GUARD) || orderState(psDroid, DORDER_ATTACKTARGET)) && weapon_slot == 0) //Watermelon:only primary slot(0) updates affect order { orderDroidObj((DROID *)psAttacker, DORDER_ATTACKTARGET, psBetterTarget); } else //can't override current order { setDroidActionTarget(psDroid, psBetterTarget, weapon_slot); } } else if (psAttacker->type == OBJ_STRUCTURE) { STRUCTURE *psBuilding = (STRUCTURE *)psAttacker; setStructureTarget(psBuilding, psBetterTarget, weapon_slot, tmpOrigin); } return true; } return false; }
// add a droid to a command group void cmdDroidAddDroid(DROID *psCommander, DROID *psDroid) { DROID_GROUP *psGroup; if (psCommander->psGroup == NULL) { if (!grpCreate(&psGroup)) { return; } grpJoin(psGroup, psCommander); psDroid->group = UBYTE_MAX; } if (grpNumMembers(psCommander->psGroup) < cmdDroidMaxGroup(psCommander)) { grpJoin(psCommander->psGroup, psDroid); psDroid->group = UBYTE_MAX; // set the secondary states for the unit secondarySetState(psDroid, DSO_ATTACK_RANGE, (SECONDARY_STATE)(psCommander->secondaryOrder & DSS_ARANGE_MASK)); secondarySetState(psDroid, DSO_REPAIR_LEVEL, (SECONDARY_STATE)(psCommander->secondaryOrder & DSS_REPLEV_MASK)); secondarySetState(psDroid, DSO_ATTACK_LEVEL, (SECONDARY_STATE)(psCommander->secondaryOrder & DSS_ALEV_MASK)); secondarySetState(psDroid, DSO_HALTTYPE, (SECONDARY_STATE)(psCommander->secondaryOrder & DSS_HALT_MASK)); orderDroidObj(psDroid, DORDER_GUARD, (BASE_OBJECT *)psCommander, ModeQueue); } }
/** This function adds the droid to the command group commanded by psCommander. * It creates a group if it doesn't exist. * If the group is not full, it adds the droid to it and sets all the droid's states and orders to the group's. */ void cmdDroidAddDroid(DROID *psCommander, DROID *psDroid) { DROID_GROUP *psGroup; if (psCommander->psGroup == NULL) { psGroup = grpCreate(); psGroup->add(psCommander); psDroid->group = UBYTE_MAX; } if (psCommander->psGroup->getNumMembers() < cmdDroidMaxGroup(psCommander)) { psCommander->psGroup->add(psDroid); psDroid->group = UBYTE_MAX; // set the secondary states for the unit secondarySetState(psDroid, DSO_REPAIR_LEVEL, (SECONDARY_STATE)(psCommander->secondaryOrder & DSS_REPLEV_MASK), ModeImmediate); secondarySetState(psDroid, DSO_ATTACK_LEVEL, (SECONDARY_STATE)(psCommander->secondaryOrder & DSS_ALEV_MASK), ModeImmediate); orderDroidObj(psDroid, DORDER_GUARD, (BASE_OBJECT *)psCommander, ModeImmediate); } else { audio_PlayTrack( ID_SOUND_BUILD_FAIL ); addConsoleMessage(_("Commander needs a higher level to command more units"), DEFAULT_JUSTIFY, SYSTEM_MESSAGE); } }
/*checks through the target players list of structures and droids to see if any support a counter battery sensor*/ void counterBatteryFire(BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget) { BASE_OBJECT *psViewer; /*if a null target is passed in ignore - this will be the case when a 'miss' projectile is sent - we may have to cater for these at some point*/ // also ignore cases where you attack your own player // Also ignore cases where there are already 1000 missiles heading towards the attacker. if (psTarget == NULL || (psAttacker != NULL && psAttacker->player == psTarget->player) || aiObjectIsProbablyDoomed(psAttacker)) { return; } CHECK_OBJECT(psTarget); for (psViewer = apsSensorList[0]; psViewer; psViewer = psViewer->psNextFunc) { if (aiCheckAlliances(psTarget->player, psViewer->player)) { if ((psViewer->type == OBJ_STRUCTURE && !structCBSensor((STRUCTURE *)psViewer)) || (psViewer->type == OBJ_DROID && !cbSensorDroid((DROID *)psViewer))) { continue; } const int sensorRange = objSensorRange(psViewer); // Check sensor distance from target const int xDiff = psViewer->pos.x - psTarget->pos.x; const int yDiff = psViewer->pos.y - psTarget->pos.y; if (xDiff * xDiff + yDiff * yDiff < sensorRange * sensorRange) { // Inform viewer of target if (psViewer->type == OBJ_DROID) { orderDroidObj((DROID *)psViewer, DORDER_OBSERVE, psAttacker, ModeImmediate); } else if (psViewer->type == OBJ_STRUCTURE) { ((STRUCTURE *)psViewer)->psTarget[0] = psAttacker; } } } } }
// Give a Droid an order to an object bool scrOrderDroidObj(void) { DROID *psDroid; DROID_ORDER order; BASE_OBJECT *psObj; if (!stackPopParams(3, ST_DROID, &psDroid, VAL_INT, &order, ST_BASEOBJECT, &psObj)) { return false; } ASSERT(psDroid != NULL, "scrOrderUnitObj: Invalid unit pointer"); ASSERT(psObj != NULL, "scrOrderUnitObj: Invalid object pointer"); if (psDroid == NULL || psObj == NULL) { return false; } if (order != DORDER_ATTACK && order != DORDER_HELPBUILD && order != DORDER_DEMOLISH && order != DORDER_REPAIR && order != DORDER_OBSERVE && order != DORDER_EMBARK && order != DORDER_FIRESUPPORT && order != DORDER_DROIDREPAIR) { ASSERT(false, "scrOrderUnitObj: Invalid order"); return false; } orderDroidObj(psDroid, order, psObj, ModeQueue); return true; }
// Give a Droid an order to an object BOOL scrOrderDroidObj(void) { DROID *psDroid; SDWORD order; BASE_OBJECT *psObj; if (!stackPopParams(3, ST_DROID, &psDroid, VAL_INT, &order, ST_BASEOBJECT, &psObj)) { return FALSE; } ASSERT((PTRVALID(psDroid, sizeof(DROID)), "scrOrderUnitObj: Invalid unit pointer")); ASSERT((PTRVALID(psObj, sizeof(BASE_OBJECT)), "scrOrderUnitObj: Invalid object pointer")); if (psDroid == NULL || psObj == NULL) { return FALSE; } if (order != DORDER_ATTACK && order != DORDER_HELPBUILD && order != DORDER_DEMOLISH && order != DORDER_REPAIR && order != DORDER_OBSERVE && order != DORDER_EMBARK && order != DORDER_FIRESUPPORT) { ASSERT((FALSE, "scrOrderUnitObj: Invalid order")); return FALSE; } orderDroidObj(psDroid, order, psObj); return TRUE; }
/*checks through the target players list of structures and droids to see if any support a counter battery sensor*/ void counterBatteryFire(BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget) { BASE_OBJECT *psViewer; /*if a null target is passed in ignore - this will be the case when a 'miss' projectile is sent - we may have to cater for these at some point*/ // also ignore cases where you attack your own player // Also ignore cases where there are already 1000 missiles heading towards the attacker. if ((psTarget == NULL) || ((psAttacker != NULL) && (psAttacker->player == psTarget->player)) || aiObjectIsProbablyDoomed(psAttacker)) { return; } CHECK_OBJECT(psTarget); gridStartIterate(psTarget->pos.x, psTarget->pos.y, PREVIOUS_DEFAULT_GRID_SEARCH_RADIUS); for (psViewer = gridIterate(); psViewer != NULL; psViewer = gridIterate()) { STRUCTURE *psStruct; DROID *psDroid; SDWORD sensorRange = 0; if (psViewer->player != psTarget->player) { //ignore non target players' objects continue; } if (psViewer->type == OBJ_STRUCTURE) { psStruct = (STRUCTURE *)psViewer; //check if have a sensor of correct type if (structCBSensor(psStruct) || structVTOLCBSensor(psStruct)) { sensorRange = psStruct->pStructureType->pSensor->range; } } else if (psViewer->type == OBJ_DROID) { psDroid = (DROID *)psViewer; //must be a CB sensor if (cbSensorDroid(psDroid)) { sensorRange = asSensorStats[psDroid->asBits[COMP_SENSOR]. nStat].range; } } //check sensor distance from target if (sensorRange) { SDWORD xDiff = (SDWORD)psViewer->pos.x - (SDWORD)psTarget->pos.x; SDWORD yDiff = (SDWORD)psViewer->pos.y - (SDWORD)psTarget->pos.y; if (xDiff*xDiff + yDiff*yDiff < sensorRange * sensorRange) { //inform viewer of target if (psViewer->type == OBJ_DROID) { orderDroidObj((DROID *)psViewer, DORDER_OBSERVE, psAttacker, ModeImmediate); } else if (psViewer->type == OBJ_STRUCTURE) { ((STRUCTURE *)psViewer)->psTarget[0] = psAttacker; } } } } }
/* Do the AI for a droid */ void aiUpdateDroid(DROID *psDroid) { BASE_OBJECT *psTarget; BOOL lookForTarget,updateTarget; ASSERT(psDroid != NULL, "Invalid droid pointer"); if (!psDroid || isDead((BASE_OBJECT *)psDroid)) { return; } // HACK: we always want to update orders when NOT running a MP game, // and we don't want to update when the droid belongs to another human player if (!myResponsibility(psDroid->player) && bMultiPlayer && isHumanPlayer(psDroid->player)) { return; // we should not order this droid around } lookForTarget = false; updateTarget = false; // look for a target if doing nothing if (orderState(psDroid, DORDER_NONE) || orderState(psDroid, DORDER_GUARD) || orderState(psDroid, DORDER_TEMP_HOLD)) { lookForTarget = true; } // but do not choose another target if doing anything while guarding if (orderState(psDroid, DORDER_GUARD) && (psDroid->action != DACTION_NONE)) { lookForTarget = false; } // except when self-repairing if (psDroid->action == DACTION_DROIDREPAIR && psDroid->psActionTarget[0] == (BASE_OBJECT *)psDroid) { lookForTarget = true; } // don't look for a target if sulking if (psDroid->action == DACTION_SULK) { lookForTarget = false; } /* Only try to update target if already have some target */ if (psDroid->action == DACTION_ATTACK || psDroid->action == DACTION_MOVEFIRE || psDroid->action == DACTION_MOVETOATTACK || psDroid->action == DACTION_ROTATETOATTACK) { updateTarget = true; } if ((orderState(psDroid, DORDER_OBSERVE) || orderState(psDroid, DORDER_ATTACKTARGET)) && psDroid->psTarget && aiObjectIsProbablyDoomed(psDroid->psTarget)) { lookForTarget = true; updateTarget = false; } /* Don't update target if we are sent to attack and reached attack destination (attacking our target) */ if (orderState(psDroid, DORDER_ATTACK) && psDroid->psActionTarget[0] == psDroid->psTarget) { updateTarget = false; } // don't look for a target if there are any queued orders if (psDroid->listSize > 0) { lookForTarget = false; updateTarget = false; } // horrible check to stop droids looking for a target if // they would switch to the guard order in the order update loop if ((psDroid->order == DORDER_NONE) && (psDroid->player == selectedPlayer) && !isVtolDroid(psDroid) && secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_GUARD) { lookForTarget = false; updateTarget = false; } // don't allow units to start attacking if they will switch to guarding the commander if(hasCommander(psDroid)) { lookForTarget = false; updateTarget = false; } if(bMultiPlayer && isVtolDroid(psDroid) && isHumanPlayer(psDroid->player)) { lookForTarget = false; updateTarget = false; } // do not look for a target if droid is currently under direct control. if(driveModeActive() && (psDroid == driveGetDriven())) { lookForTarget = false; updateTarget = false; } // CB and VTOL CB droids can't autotarget. if (psDroid->droidType == DROID_SENSOR && !standardSensorDroid(psDroid)) { lookForTarget = false; updateTarget = false; } // do not attack if the attack level is wrong if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) != DSS_ALEV_ALWAYS) { lookForTarget = false; } /* For commanders and non-assigned non-commanders: look for a better target once in a while */ if(!lookForTarget && updateTarget) { if((psDroid->numWeaps > 0) && !hasCommander(psDroid)) //not assigned to commander { if((psDroid->id + gameTime)/TARGET_UPD_SKIP_FRAMES != (psDroid->id + gameTime - deltaGameTime)/TARGET_UPD_SKIP_FRAMES) { unsigned int i; (void)updateAttackTarget((BASE_OBJECT*)psDroid, 0); // this function always has to be called on weapon-slot 0 (even if ->numWeaps == 0) //updates all targets for (i = 1; i < psDroid->numWeaps; ++i) { (void)updateAttackTarget((BASE_OBJECT*)psDroid, i); } } } } /* Null target - see if there is an enemy to attack */ if (lookForTarget && !updateTarget) { if (psDroid->droidType == DROID_SENSOR) { if (aiChooseSensorTarget((BASE_OBJECT *)psDroid, &psTarget)) { orderDroidObj(psDroid, DORDER_OBSERVE, psTarget); } } else { if (aiChooseTarget((BASE_OBJECT *)psDroid, &psTarget, 0, true, NULL)) { orderDroidObj(psDroid, DORDER_ATTACKTARGET, psTarget); } } } }
// //////////////////////////////////////////////////////////////////////////// // receive a check and update the local world state accordingly BOOL recvDroidCheck(NETQUEUE queue) { uint8_t count; int i; uint32_t synchTime; NETbeginDecode(queue, GAME_CHECK_DROID); // Get the number of droids to expect NETuint8_t(&count); NETuint32_t(&synchTime); // Get game time. for (i = 0; i < count; i++) { DROID * pD; PACKAGED_CHECK pc, pc2; Position precPos; NETPACKAGED_CHECK(&pc); // Find the droid in question if (!IdToDroid(pc.droidID, pc.player, &pD)) { NETlogEntry("Recvd Unknown droid info. val=player", SYNC_FLAG, pc.player); debug(LOG_SYNC, "Received checking info for an unknown (as yet) droid. player:%d ref:%d", pc.player, pc.droidID); continue; } syncDebugDroid(pD, '<'); if (pD->gameCheckDroid == NULL) { debug(LOG_SYNC, "We got a droid %u synch, but we couldn't find the droid!", pc.droidID); continue; // Can't synch, since we didn't save data to be able to calculate a delta. } pc2 = *(PACKAGED_CHECK *)pD->gameCheckDroid; // pc2 should be declared here, as const. if (pc2.gameTime != synchTime + MIN_DELAY_BETWEEN_DROID_SYNCHS) { debug(LOG_SYNC, "We got a droid %u synch, but we didn't choose the same droid to synch.", pc.droidID); ((PACKAGED_CHECK *)pD->gameCheckDroid)->gameTime = synchTime + MIN_DELAY_BETWEEN_DROID_SYNCHS; // Get droid synch time back in synch. continue; // Can't synch, since we didn't save data to be able to calculate a delta. } #define MERGECOPY(x, y, z) if (pc.y != pc2.y) { debug(LOG_SYNC, "Droid %u out of synch, changing "#x" from %"z" to %"z".", pc.droidID, x, pc.y); x = pc.y; } #define MERGEDELTA(x, y, z) if (pc.y != pc2.y) { debug(LOG_SYNC, "Droid %u out of synch, changing "#x" from %"z" to %"z".", pc.droidID, x, x + pc.y - pc2.y); x += pc.y - pc2.y; } // player not synched here... precPos = droidGetPrecisePosition(pD); MERGEDELTA(precPos.x, pos.x, "d"); MERGEDELTA(precPos.y, pos.y, "d"); MERGEDELTA(precPos.z, pos.z, "d"); droidSetPrecisePosition(pD, precPos); MERGEDELTA(pD->rot.direction, rot.direction, "d"); MERGEDELTA(pD->rot.pitch, rot.pitch, "d"); MERGEDELTA(pD->rot.roll, rot.roll, "d"); MERGEDELTA(pD->body, body, "u"); if (pD->body > pD->originalBody) { pD->body = pD->originalBody; debug(LOG_SYNC, "Droid %u body was too high after synch, reducing to %u.", pc.droidID, pD->body); } MERGEDELTA(pD->experience, experience, "u"); if (pc.pos.x != pc2.pos.x || pc.pos.y != pc2.pos.y) { // snap droid(if on ground) to terrain level at x,y. if ((asPropulsionStats + pD->asBits[COMP_PROPULSION].nStat)->propulsionType != PROPULSION_TYPE_LIFT) // if not airborne. { pD->pos.z = map_Height(pD->pos.x, pD->pos.y); } } // Doesn't cover all cases, but at least doesn't actively break stuff randomly. switch (pc.order) { case DORDER_MOVE: if (pc.order != pc2.order || pc.orderX != pc2.orderX || pc.orderY != pc2.orderY) { debug(LOG_SYNC, "Droid %u out of synch, changing order from %s to %s(%d, %d).", pc.droidID, getDroidOrderName(pc2.order), getDroidOrderName(pc.order), pc.orderX, pc.orderY); // reroute the droid. orderDroidLoc(pD, pc.order, pc.orderX, pc.orderY, ModeImmediate); } break; case DORDER_ATTACK: if (pc.order != pc2.order || pc.targetID != pc2.targetID) { BASE_OBJECT *obj = IdToPointer(pc.targetID, ANYPLAYER); if (obj != NULL) { debug(LOG_SYNC, "Droid %u out of synch, changing order from %s to %s(%u).", pc.droidID, getDroidOrderName(pc2.order), getDroidOrderName(pc.order), pc.targetID); // remote droid is attacking, not here tho! orderDroidObj(pD, pc.order, IdToPointer(pc.targetID, ANYPLAYER), ModeImmediate); } else { debug(LOG_SYNC, "Droid %u out of synch, would change order from %s to %s(%u), but can't find target.", pc.droidID, getDroidOrderName(pc2.order), getDroidOrderName(pc.order), pc.targetID); } } break; case DORDER_NONE: case DORDER_GUARD: if (pc.order != pc2.order) { DROID_ORDER_DATA sOrder; memset(&sOrder, 0, sizeof(DROID_ORDER_DATA)); sOrder.order = pc.order; debug(LOG_SYNC, "Droid %u out of synch, changing order from %s to %s.", pc.droidID, getDroidOrderName(pc2.order), getDroidOrderName(pc.order)); turnOffMultiMsg(true); moveStopDroid(pD); orderDroidBase(pD, &sOrder); turnOffMultiMsg(false); } break; default: break; // Don't know what to do, but at least won't be actively breaking anything. } MERGECOPY(pD->secondaryOrder, secondaryOrder, "u"); // The old code set this after changing orders, so doing that in case. #undef MERGECOPY #undef MERGEDELTA syncDebugDroid(pD, '>'); // ...and repeat! } NETend(); return true; }