// //////////////////////////////////////////////////////////////////////////// // receive droid information form other players. BOOL recvDroidInfo(NETQUEUE queue) { NETbeginDecode(queue, GAME_DROIDINFO); { QueuedDroidInfo info; memset(&info, 0x00, sizeof(info)); NETQueuedDroidInfo(&info); STRUCTURE_STATS *psStats = NULL; if (info.order == DORDER_BUILD || info.order == DORDER_LINEBUILD) { // Find structure target for (unsigned typeIndex = 0; typeIndex < numStructureStats; typeIndex++) { if (asStructureStats[typeIndex].ref == info.structRef) { psStats = asStructureStats + typeIndex; break; } } } if (info.subType) { syncDebug("Order=%s,%d(%d)", getDroidOrderName(info.order), info.destId, info.destType); } else { syncDebug("Order=%s,(%d,%d)", getDroidOrderName(info.order), info.x, info.y); } DROID_ORDER_DATA sOrder; memset(&sOrder, 0x00, sizeof(sOrder)); sOrder.order = info.order; sOrder.x = info.x; sOrder.y = info.y; sOrder.x2 = info.x2; sOrder.y2 = info.y2; sOrder.direction = info.direction; sOrder.psObj = processDroidTarget(info.destType, info.destId); sOrder.psStats = (BASE_STATS *)psStats; uint32_t num = 0; NETuint32_t(&num); for (unsigned n = 0; n < num; ++n) { // Get the next droid ID which is being given this order. uint32_t deltaDroidId = 0; NETuint32_t(&deltaDroidId); info.droidId += deltaDroidId; DROID *psDroid = NULL; if (!IdToDroid(info.droidId, ANYPLAYER, &psDroid)) { debug(LOG_NEVER, "Packet from %d refers to non-existent droid %u, [%s : p%d]", queue.index, info.droidId, isHumanPlayer(info.player) ? "Human" : "AI", info.player); syncDebug("Droid %d missing", info.droidId); continue; // Can't find the droid, so skip this droid. } CHECK_DROID(psDroid); syncDebugDroid(psDroid, '<'); psDroid->waitingForOwnReceiveDroidInfoMessage = false; /* * If the current order not is a command order and we are not a * commander yet are in the commander group remove us from it. */ if (hasCommander(psDroid)) { grpLeave(psDroid->psGroup, psDroid); } if (sOrder.psObj != TargetMissing) // Only do order if the target didn't die. { orderDroidBase(psDroid, &sOrder); } syncDebugDroid(psDroid, '>'); CHECK_DROID(psDroid); } } NETend(); return true; }
/* Calculates attack priority for a certain target */ static SDWORD targetAttackWeight(BASE_OBJECT *psTarget, BASE_OBJECT *psAttacker, SDWORD weapon_slot) { SDWORD targetTypeBonus=0, damageRatio=0, attackWeight=0, noTarget=-1; UDWORD weaponSlot; DROID *targetDroid=NULL,*psAttackerDroid=NULL,*psGroupDroid,*psDroid; STRUCTURE *targetStructure=NULL; WEAPON_EFFECT weaponEffect; WEAPON_STATS *attackerWeapon; BOOL bEmpWeap=false,bCmdAttached=false,bTargetingCmd=false; if (psTarget == NULL || psAttacker == NULL || aiObjectIsProbablyDoomed(psTarget)) { return noTarget; } ASSERT(psTarget != psAttacker, "targetAttackWeight: Wanted to evaluate the worth of attacking ourselves..."); targetTypeBonus = 0; //Sensors/ecm droids, non-military structures get lower priority /* Get attacker weapon effect */ if(psAttacker->type == OBJ_DROID) { psAttackerDroid = (DROID *)psAttacker; attackerWeapon = (WEAPON_STATS *)(asWeaponStats + psAttackerDroid->asWeaps[weapon_slot].nStat); //check if this droid is assigned to a commander bCmdAttached = hasCommander(psAttackerDroid); //find out if current target is targeting our commander if(bCmdAttached) { if(psTarget->type == OBJ_DROID) { psDroid = (DROID *)psTarget; //go through all enemy weapon slots for(weaponSlot = 0; !bTargetingCmd && weaponSlot < ((DROID *)psTarget)->numWeaps; weaponSlot++) { //see if this weapon is targeting our commander if (psDroid->psActionTarget[weaponSlot] == (BASE_OBJECT *)psAttackerDroid->psGroup->psCommander) { bTargetingCmd = true; } } } else { if(psTarget->type == OBJ_STRUCTURE) { //go through all enemy weapons for(weaponSlot = 0; !bTargetingCmd && weaponSlot < ((STRUCTURE *)psTarget)->numWeaps; weaponSlot++) { if (((STRUCTURE *)psTarget)->psTarget[weaponSlot] == (BASE_OBJECT *)psAttackerDroid->psGroup->psCommander) { bTargetingCmd = true; } } } } } } else if(psAttacker->type == OBJ_STRUCTURE) { attackerWeapon = ((WEAPON_STATS *)(asWeaponStats + ((STRUCTURE *)psAttacker)->asWeaps[weapon_slot].nStat)); } else /* feature */ { ASSERT(!"invalid attacker object type", "targetAttackWeight: Invalid attacker object type"); return noTarget; } //Get weapon effect weaponEffect = attackerWeapon->weaponEffect; //See if attacker is using an EMP weapon bEmpWeap = (attackerWeapon->weaponSubClass == WSC_EMP); /* Calculate attack weight */ if(psTarget->type == OBJ_DROID) { targetDroid = (DROID *)psTarget; if (targetDroid->died) { debug(LOG_NEVER, "Target droid is dead, skipping invalid droid.\n"); return noTarget; } /* Calculate damage this target suffered */ if (targetDroid->originalBody == 0) // FIXME Somewhere we get 0HP droids from { damageRatio = 0; debug(LOG_ERROR, "targetAttackWeight: 0HP droid detected!"); debug(LOG_ERROR, " Type: %i Name: \"%s\" Owner: %i \"%s\")", targetDroid->droidType, targetDroid->aName, targetDroid->player, getPlayerName(targetDroid->player)); } else { damageRatio = 1 - targetDroid->body / targetDroid->originalBody; } assert(targetDroid->originalBody != 0); // Assert later so we get the info from above /* See if this type of a droid should be prioritized */ switch (targetDroid->droidType) { case DROID_SENSOR: case DROID_ECM: case DROID_PERSON: case DROID_TRANSPORTER: case DROID_DEFAULT: case DROID_ANY: break; case DROID_CYBORG: case DROID_WEAPON: case DROID_CYBORG_SUPER: targetTypeBonus = WEIGHT_WEAPON_DROIDS; break; case DROID_COMMAND: targetTypeBonus = WEIGHT_COMMAND_DROIDS; break; case DROID_CONSTRUCT: case DROID_REPAIR: case DROID_CYBORG_CONSTRUCT: case DROID_CYBORG_REPAIR: targetTypeBonus = WEIGHT_SERVICE_DROIDS; break; } /* Now calculate the overall weight */ attackWeight = asWeaponModifier[weaponEffect][(asPropulsionStats + targetDroid->asBits[COMP_PROPULSION].nStat)->propulsionType] // Our weapon's effect against target + WEIGHT_DIST_TILE_DROID * psAttacker->sensorRange/TILE_UNITS - WEIGHT_DIST_TILE_DROID * map_coord(iHypot(psAttacker->pos.x - targetDroid->pos.x, psAttacker->pos.y - targetDroid->pos.y)) // farer droids are less attractive + WEIGHT_HEALTH_DROID * damageRatio // we prefer damaged droids + targetTypeBonus; // some droid types have higher priority /* If attacking with EMP try to avoid targets that were already "EMPed" */ if(bEmpWeap && (targetDroid->lastHitWeapon == WSC_EMP) && ((gameTime - targetDroid->timeLastHit) < EMP_DISABLE_TIME)) //target still disabled { attackWeight /= EMP_DISABLED_PENALTY_F; } } else if(psTarget->type == OBJ_STRUCTURE) { targetStructure = (STRUCTURE *)psTarget; /* Calculate damage this target suffered */ damageRatio = 1 - targetStructure->body / structureBody(targetStructure); /* See if this type of a structure should be prioritized */ switch(targetStructure->pStructureType->type) { case REF_DEFENSE: targetTypeBonus = WEIGHT_WEAPON_STRUCT; break; case REF_RESOURCE_EXTRACTOR: targetTypeBonus = WEIGHT_DERRICK_STRUCT; break; case REF_FACTORY: case REF_CYBORG_FACTORY: case REF_REPAIR_FACILITY: targetTypeBonus = WEIGHT_MILITARY_STRUCT; break; default: break; } /* Now calculate the overall weight */ attackWeight = asStructStrengthModifier[weaponEffect][targetStructure->pStructureType->strength] // Our weapon's effect against target + WEIGHT_DIST_TILE_STRUCT * psAttacker->sensorRange/TILE_UNITS - WEIGHT_DIST_TILE_STRUCT * map_coord(iHypot(psAttacker->pos.x - targetStructure->pos.x, psAttacker->pos.y - targetStructure->pos.y)) // farer structs are less attractive + WEIGHT_HEALTH_STRUCT * damageRatio // we prefer damaged structures + targetTypeBonus; // some structure types have higher priority /* Go for unfinished structures only if nothing else found (same for non-visible structures) */ if(targetStructure->status != SS_BUILT) //a decoy? { attackWeight /= WEIGHT_STRUCT_NOTBUILT_F; } /* EMP should only attack structures if no enemy droids are around */ if(bEmpWeap) { attackWeight /= EMP_STRUCT_PENALTY_F; } } else //a feature { return 1; } /* We prefer objects we can see and can attack immediately */ if(!visibleObject((BASE_OBJECT *)psAttacker, psTarget, true)) { attackWeight /= WEIGHT_NOT_VISIBLE_F; } /* Commander-related criterias */ if(bCmdAttached) //attached to a commander and don't have a target assigned by some order { ASSERT(psAttackerDroid->psGroup->psCommander != NULL, "Commander is NULL"); //if commander is being targeted by our target, try to defend the commander if(bTargetingCmd) { attackWeight += WEIGHT_CMD_RANK * ( 1 + getDroidLevel(psAttackerDroid->psGroup->psCommander)); } //fire support - go through all droids assigned to the commander for (psGroupDroid = psAttackerDroid->psGroup->psList; psGroupDroid; psGroupDroid = psGroupDroid->psGrpNext) { for(weaponSlot = 0; weaponSlot < psGroupDroid->numWeaps; weaponSlot++) { //see if this droid is currently targeting current target if(psGroupDroid->psTarget == psTarget || psGroupDroid->psActionTarget[weaponSlot] == psTarget) { //we prefer targets that are already targeted and hence will be destroyed faster attackWeight += WEIGHT_CMD_SAME_TARGET; } } } } return attackWeight; }
/* 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; } lookForTarget = false; updateTarget = false; // look for a target if doing nothing if (orderState(psDroid, DORDER_NONE) || orderState(psDroid, DORDER_GUARD) || orderState(psDroid, DORDER_HOLD)) { lookForTarget = true; } // but do not choose another target if doing anything while guarding // exception for sensors, to allow re-targetting when target is doomed if (orderState(psDroid, DORDER_GUARD) && psDroid->action != DACTION_NONE && psDroid->droidType != DROID_SENSOR) { lookForTarget = false; } // 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->order.psObj && psDroid->order.psObj->died) { 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->order.psObj) { updateTarget = false; } // don't look for a target if there are any queued orders if (psDroid->listSize > 0) { 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 && psDroid->numWeaps > 0 && !hasCommander(psDroid) && (psDroid->id + gameTime) / TARGET_UPD_SKIP_FRAMES != (psDroid->id + gameTime - deltaGameTime) / TARGET_UPD_SKIP_FRAMES) { for (int i = 0; i < psDroid->numWeaps; ++i) { 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)) { actionDroid(psDroid, DACTION_OBSERVE, psTarget); } } else { if (aiChooseTarget((BASE_OBJECT *)psDroid, &psTarget, 0, true, NULL)) { actionDroid(psDroid, DACTION_ATTACK, psTarget); } } } }
/* 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 droid information form other players. bool recvDroidInfo(NETQUEUE queue) { NETbeginDecode(queue, GAME_DROIDINFO); { QueuedDroidInfo info; memset(&info, 0x00, sizeof(info)); NETQueuedDroidInfo(&info); STRUCTURE_STATS *psStats = NULL; if (info.subType == LocOrder && (info.order == DORDER_BUILD || info.order == DORDER_LINEBUILD)) { // Find structure target for (unsigned typeIndex = 0; typeIndex < numStructureStats; typeIndex++) { if (asStructureStats[typeIndex].ref == info.structRef) { psStats = asStructureStats + typeIndex; break; } } } switch (info.subType) { case ObjOrder: syncDebug("Order=%s,%d(%d)", getDroidOrderName(info.order), info.destId, info.destType); break; case LocOrder: syncDebug("Order=%s,(%d,%d)", getDroidOrderName(info.order), info.pos.x, info.pos.y); break; case SecondaryOrder: syncDebug("SecondaryOrder=%d,%08X", (int)info.secOrder, (int)info.secState); break; } DROID_ORDER_DATA sOrder = infoToOrderData(info, psStats); uint32_t num = 0; NETuint32_t(&num); for (unsigned n = 0; n < num; ++n) { // Get the next droid ID which is being given this order. uint32_t deltaDroidId = 0; NETuint32_t(&deltaDroidId); info.droidId += deltaDroidId; DROID *psDroid = IdToDroid(info.droidId, info.player); if (!psDroid) { debug(LOG_NEVER, "Packet from %d refers to non-existent droid %u, [%s : p%d]", queue.index, info.droidId, isHumanPlayer(info.player) ? "Human" : "AI", info.player); syncDebug("Droid %d missing", info.droidId); continue; // Can't find the droid, so skip this droid. } CHECK_DROID(psDroid); syncDebugDroid(psDroid, '<'); switch (info.subType) { case ObjOrder: case LocOrder: /* * If the current order not is a command order and we are not a * commander yet are in the commander group remove us from it. */ if (hasCommander(psDroid)) { psDroid->psGroup->remove(psDroid); } if (sOrder.psObj != TargetMissing) // Only do order if the target didn't die. { if (!info.add) { orderDroidListEraseRange(psDroid, 0, psDroid->listSize + 1); // Clear all non-pending orders, plus the first pending order (which is probably the order we just received). orderDroidBase(psDroid, &sOrder); // Execute the order immediately (even if in the middle of another order. } else { orderDroidAdd(psDroid, &sOrder); // Add the order to the (non-pending) list. Will probably overwrite the corresponding pending order, assuming all pending orders were written to the list. } } break; case SecondaryOrder: // Set the droids secondary order turnOffMultiMsg(true); secondarySetState(psDroid, info.secOrder, info.secState); turnOffMultiMsg(false); break; } syncDebugDroid(psDroid, '>'); CHECK_DROID(psDroid); } } NETend(); return true; }