/** * @brief Debug function to print a player's inventory */ void G_InvList_f (const Player& player) { Edict* ent = nullptr; gi.DPrintf("Print inventory for '%s'\n", player.pers.netname); while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, player.getTeam()))) { gi.DPrintf("actor: '%s'\n", ent->chr.name); const Container* cont = nullptr; while ((cont = ent->chr.inv.getNextCont(cont, true))) { Com_Printf("Container: %i\n", cont->id); Item* item = nullptr; while ((item = cont->getNextItem(item))) { Com_Printf(".. item.def(): %i, item.ammo: %i, item.ammoLeft: %i, x: %i, y: %i\n", (item->def() ? item->def()->idx : NONE), (item->ammoDef() ? item->ammoDef()->idx : NONE), item->getAmmoLeft(), item->getX(), item->getY()); if (item->def()) Com_Printf(".... weapon: %s\n", item->def()->id); if (item->ammoDef()) Com_Printf(".... ammo: %s (%i)\n", item->ammoDef()->id, item->getAmmoLeft()); } } const float invWeight = ent->chr.inv.getWeight(); const int maxWeight = ent->chr.score.skills[ABILITY_POWER]; const float penalty = GET_ENCUMBRANCE_PENALTY(invWeight, maxWeight); const int normalTU = GET_TU(ent->chr.score.skills[ABILITY_SPEED], 1.0f - WEIGHT_NORMAL_PENALTY); const int tus = GET_TU(ent->chr.score.skills[ABILITY_SPEED], penalty); const int tuPenalty = tus - normalTU; const char* penaltyStr = 1.0f - penalty < WEIGHT_NORMAL_PENALTY ? "'Light weight'" : (1.0f - penalty < WEIGHT_HEAVY_PENALTY ? "'Normal weight'" : "'Encumbered'"); Com_Printf("Weight: %g/%i, Encumbrance: %s (%.0f%%), TU's: %i (normal: %i, penalty/bonus: %+i)\n", invWeight, maxWeight, penaltyStr, invWeight / maxWeight * 100.0f, tus, normalTU, tuPenalty); } }
int G_ActorCalculateMaxTU (const Edict* ent) { const int invWeight = ent->chr.inv.getWeight(); const int currentMaxTU = GET_TU(ent->chr.score.skills[ABILITY_SPEED], GET_ENCUMBRANCE_PENALTY(invWeight, ent->chr.score.skills[ABILITY_POWER])) * G_ActorGetInjuryPenalty(ent, MODIFIER_TU); return std::min(currentMaxTU, MAX_TU); }
/** * @brief Determines the amount of XP earned by a given soldier for a given skill, based on the soldier's performance in the last mission. * @param[in] skill The skill for which to fetch the maximum amount of XP. * @param[in] ent Pointer to the character you want to get the earned experience for * @sa G_UpdateCharacterExperience * @sa G_GetMaxExperiencePerMission */ static int G_GetEarnedExperience (abilityskills_t skill, edict_t *ent) { character_t *chr = &ent->chr; int experience = 0; int i; switch (skill) { case ABILITY_POWER: { const float weight = chr->scoreMission->carriedWeight / level.actualRound; const float penalty = GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER]); experience = 150 * (weight / chr->score.skills[ABILITY_POWER]) / penalty; break; } case ABILITY_SPEED: experience += chr->scoreMission->movedNormal / 2 + chr->scoreMission->movedCrouched; /* skip skills < ABILITY_NUM_TYPES, they are abilities not real skills */ for (i = ABILITY_NUM_TYPES; i < SKILL_NUM_TYPES; i++) experience += (chr->scoreMission->firedTUs[i] + chr->scoreMission->firedSplashTUs[i]) / 10; break; case ABILITY_ACCURACY: /* skip skills < ABILITY_NUM_TYPES, they are abilities not real skills */ for (i = ABILITY_NUM_TYPES; i < SKILL_NUM_TYPES; i++) if (i == SKILL_SNIPER) experience += 30 * (chr->scoreMission->hits[i][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[i][KILLED_ENEMIES]); else experience += 20 * (chr->scoreMission->hits[i][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[i][KILLED_ENEMIES]); break; case ABILITY_MIND: experience = 50 + 200 * chr->scoreMission->kills[KILLED_ENEMIES]; break; case SKILL_CLOSE: experience = 150 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; case SKILL_HEAVY: experience = 200 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; case SKILL_ASSAULT: experience = 100 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; case SKILL_SNIPER: experience = 200 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; case SKILL_EXPLOSIVE: experience = 200 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; default: break; } return experience; }
/** * @brief Update the equipment weight for the selected actor. */ static void INV_UpdateActorLoad_f (void) { if (Cmd_Argc() < 2) { Com_Printf("Usage: %s <callback>\n", Cmd_Argv(0)); return; } const character_t* chr = GAME_GetSelectedChr(); if (chr == nullptr) return; const float invWeight = chr->inv.getWeight(); const int maxWeight = GAME_GetChrMaxLoad(chr); const float penalty = GET_ENCUMBRANCE_PENALTY(invWeight, maxWeight); const int normalTU = GET_TU(chr->score.skills[ABILITY_SPEED], 1.0f - WEIGHT_NORMAL_PENALTY); const int tus = GET_TU(chr->score.skills[ABILITY_SPEED], penalty); const int tuPenalty = tus - normalTU; int count = 0; const Container* cont = nullptr; while ((cont = chr->inv.getNextCont(cont))) { if (cont->def()->temp) continue; for (Item* invList = cont->_invList, *next; invList; invList = next) { next = invList->getNext(); const fireDef_t* fireDef = invList->getFiredefs(); if (fireDef == nullptr) continue; for (int i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) { if (fireDef[i].time <= 0) continue; if (fireDef[i].time <= tus) continue; if (count <= 0) Com_sprintf(popupText, sizeof(popupText), _("This soldier no longer has enough TUs to use the following items:\n\n")); Q_strcat(popupText, sizeof(popupText), "%s: %s (%i)\n", _(invList->def()->name), _(fireDef[i].name), fireDef[i].time); ++count; } } } if ((Cmd_Argc() < 3 || atoi(Cmd_Argv(2)) == 0) && count > 0) UI_Popup(_("Warning"), popupText); char label[MAX_VAR]; char tooltip[MAX_VAR]; Com_sprintf(label, sizeof(label), "%g/%i %s %s", invWeight / WEIGHT_FACTOR, maxWeight, _("Kg"), (count > 0 ? _("Warning!") : "")); Com_sprintf(tooltip, sizeof(tooltip), "%s %i (%+i)", _("TU:"), tus, tuPenalty); UI_ExecuteConfunc("%s \"%s\" \"%s\" %f %i", Cmd_Argv(1), label, tooltip, WEIGHT_NORMAL_PENALTY - (1.0f - penalty), count); }
/** * @brief Determines the amount of XP earned by a given soldier for a given skill, based on the soldier's performance in the last mission. * @param[in] skill The skill for which to fetch the maximum amount of XP. * @param[in] ent Pointer to the character you want to get the earned experience for * @sa G_UpdateCharacterExperience * @sa G_GetMaxExperiencePerMission */ static int G_GetEarnedExperience (abilityskills_t skill, Edict* ent) { character_t* chr = &ent->chr; int experience = 0; switch (skill) { case ABILITY_POWER: { const float weight = chr->scoreMission->carriedWeight / WEIGHT_FACTOR / level.actualRound; const float penalty = GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER]); experience = 50 * (weight / chr->score.skills[ABILITY_POWER]) / penalty; break; } case ABILITY_ACCURACY: /* skip skills < ABILITY_NUM_TYPES, they are abilities not real skills */ for (int i = ABILITY_NUM_TYPES; i < SKILL_NUM_TYPES; i++) if (i == SKILL_SNIPER) experience += 60 * (chr->scoreMission->hits[i][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[i][KILLED_ENEMIES]); else experience += 40 * (chr->scoreMission->hits[i][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[i][KILLED_ENEMIES]); break; case ABILITY_MIND: experience = 50 + 100 * chr->scoreMission->kills[KILLED_ENEMIES]; break; case SKILL_CLOSE: experience = 180 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; #if 0 case SKILL_HEAVY: experience = 180 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; #endif case SKILL_ASSAULT: experience = 180 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; case SKILL_SNIPER: experience = 180 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; case SKILL_EXPLOSIVE: experience = 180 * (chr->scoreMission->hits[skill][KILLED_ENEMIES] + chr->scoreMission->hitsSplash[skill][KILLED_ENEMIES]); break; default: break; } return experience; }
/** * @brief Fully equip one actor. The equipment that is added to the inventory of the given actor * is taken from the equipment script definition. * @param[in] chr The character that will get the weapon. * @param[in] ed The equipment that is added from to the actors inventory * @param[in] maxWeight The max weight this actor is allowed to carry. * @note The code below is a complete implementation * of the scheme sketched at the beginning of equipment_missions.ufo. * Beware: If two weapons in the same category have the same price, * only one will be considered for inventory. */ void InventoryInterface::EquipActorNormal (character_t* const chr, const equipDef_t* ed, int maxWeight) { const teamDef_t* td = chr->teamDef; const int numEquip = lengthof(ed->numItems); int repeat = 0; if (td->weapons) { int missedPrimary = 0; /**< If actor has a primary weapon, this is zero. Otherwise, this is the probability * 100 * that the actor had to get a primary weapon (used to compensate the lack of primary weapon) */ const objDef_t* primaryWeapon = nullptr; /* Primary weapons */ const int maxWeaponIdx = std::min(this->csi->numODs - 1, numEquip - 1); int randNumber = rand() % 100; for (int i = 0; i < maxWeaponIdx; i++) { const objDef_t* obj = INVSH_GetItemByIDX(i); if (ed->numItems[i] && obj->weapon && obj->fireTwoHanded && obj->isPrimary) { randNumber -= ed->numItems[i]; missedPrimary += ed->numItems[i]; if (!primaryWeapon && randNumber < 0) primaryWeapon = obj; } } /* See if a weapon has been selected. */ equipPrimaryWeaponType_t primary = WEAPON_NO_PRIMARY; int hasWeapon = 0; if (primaryWeapon) { hasWeapon += PackAmmoAndWeapon(chr, primaryWeapon, 0, ed, maxWeight); if (hasWeapon) { int ammo; /* Find the first possible ammo to check damage type. */ for (ammo = 0; ammo < this->csi->numODs; ammo++) if (ed->numItems[ammo] && this->csi->ods[ammo].isLoadableInWeapon(primaryWeapon)) break; if (ammo < this->csi->numODs) { if (/* To avoid two particle weapons. */ !(this->csi->ods[ammo].dmgtype == this->csi->damParticle) /* To avoid SMG + Assault Rifle */ && !(this->csi->ods[ammo].dmgtype == this->csi->damNormal)) { primary = WEAPON_OTHER; } else { primary = WEAPON_PARTICLE_OR_NORMAL; } } /* reset missedPrimary: we got a primary weapon */ missedPrimary = 0; } else { Com_DPrintf(DEBUG_SHARED, "INVSH_EquipActor: primary weapon '%s' couldn't be equipped in equipment '%s' (%s).\n", primaryWeapon->id, ed->id, invName); repeat = WEAPONLESS_BONUS > frand(); } } /* Sidearms (secondary weapons with reload). */ do { int randNumber = rand() % 100; const objDef_t* secondaryWeapon = nullptr; for (int i = 0; i < this->csi->numODs; i++) { const objDef_t* obj = INVSH_GetItemByIDX(i); if (ed->numItems[i] && obj->weapon && obj->isReloadable() && !obj->deplete && obj->isSecondary) { randNumber -= ed->numItems[i] / (primary == WEAPON_PARTICLE_OR_NORMAL ? 2 : 1); if (randNumber < 0) { secondaryWeapon = obj; break; } } } if (secondaryWeapon) { hasWeapon += PackAmmoAndWeapon(chr, secondaryWeapon, missedPrimary, ed, maxWeight); } } while (!hasWeapon && repeat--); /* Misc items and secondary weapons without reload. */ if (!hasWeapon) repeat = WEAPONLESS_BONUS > frand(); else repeat = 0; /* Misc object probability can be bigger than 100 -- you're sure to * have one misc if it fits your backpack */ int sum = 0; for (int i = 0; i < this->csi->numODs; i++) { const objDef_t* obj = INVSH_GetItemByIDX(i); if (ed->numItems[i] && ((obj->weapon && obj->isSecondary && (!obj->isReloadable() || obj->deplete)) || obj->isMisc)) { /* if ed->num[i] is greater than 100, the first number is the number of items you'll get: * don't take it into account for probability * Make sure that the probability is at least one if an item can be selected */ sum += ed->numItems[i] ? std::max(ed->numItems[i] % 100, 1) : 0; } } if (sum) { do { int randNumber = rand() % sum; const objDef_t* secondaryWeapon = nullptr; for (int i = 0; i < this->csi->numODs; i++) { const objDef_t* obj = INVSH_GetItemByIDX(i); if (ed->numItems[i] && ((obj->weapon && obj->isSecondary && (!obj->isReloadable() || obj->deplete)) || obj->isMisc)) { randNumber -= ed->numItems[i] ? std::max(ed->numItems[i] % 100, 1) : 0; if (randNumber < 0) { secondaryWeapon = obj; break; } } } if (secondaryWeapon) { int num = ed->numItems[secondaryWeapon->idx] / 100 + (ed->numItems[secondaryWeapon->idx] % 100 >= 100 * frand()); while (num--) { hasWeapon += PackAmmoAndWeapon(chr, secondaryWeapon, 0, ed, maxWeight); } } } while (repeat--); /* Gives more if no serious weapons. */ } /* If no weapon at all, bad guys will always find a blade to wield. */ if (!hasWeapon) { int maxPrice = 0; const objDef_t* blade = nullptr; Com_DPrintf(DEBUG_SHARED, "INVSH_EquipActor: no weapon picked in equipment '%s', defaulting to the most expensive secondary weapon without reload. (%s)\n", ed->id, invName); for (int i = 0; i < this->csi->numODs; i++) { const objDef_t* obj = INVSH_GetItemByIDX(i); if (ed->numItems[i] && obj->weapon && obj->isSecondary && !obj->isReloadable()) { if (obj->price > maxPrice) { maxPrice = obj->price; blade = obj; } } } if (maxPrice) hasWeapon += PackAmmoAndWeapon(chr, blade, 0, ed, maxWeight); } /* If still no weapon, something is broken, or no blades in equipment. */ if (!hasWeapon) Com_DPrintf(DEBUG_SHARED, "INVSH_EquipActor: cannot add any weapon; no secondary weapon without reload detected for equipment '%s' (%s).\n", ed->id, invName); /* Armour; especially for those without primary weapons. */ repeat = (float) missedPrimary > frand() * 100.0; } else { return; } Inventory* const inv = &chr->inv; const int speed = chr->score.skills[ABILITY_SPEED]; if (td->armour) { do { int randNumber = rand() % 100; for (int i = 0; i < this->csi->numODs; i++) { const objDef_t* armour = INVSH_GetItemByIDX(i); if (ed->numItems[i] && armour->isArmour()) { randNumber -= ed->numItems[i]; if (randNumber < 0) { const Item item(armour); int tuNeed = 0; const int weight = GetInventoryState(inv, tuNeed) + item.getWeight(); const int maxTU = GET_TU(speed, GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER])); if (weight > maxWeight * WEIGHT_FACTOR || tuNeed > maxTU) continue; if (tryAddToInventory(inv, &item, &this->csi->ids[CID_ARMOUR])) { repeat = 0; break; } } } } } while (repeat-- > 0); } else { Com_DPrintf(DEBUG_SHARED, "INVSH_EquipActor: teamdef '%s' may not carry armour (%s)\n", td->name, invName); } { int randNumber = rand() % 10; for (int i = 0; i < this->csi->numODs; i++) { if (ed->numItems[i]) { const objDef_t* miscItem = INVSH_GetItemByIDX(i); if (miscItem->isMisc && !miscItem->weapon) { randNumber -= ed->numItems[i]; if (randNumber < 0) { const bool oneShot = miscItem->oneshot; const Item item(miscItem, oneShot ? miscItem : nullptr, oneShot ? miscItem->ammo : NONE_AMMO); containerIndex_t container; int tuNeed; const fireDef_t* itemFd = item.getSlowestFireDef(); const float weight = GetInventoryState(inv, tuNeed) + item.getWeight(); const int maxTU = GET_TU(speed, GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER])); if (miscItem->headgear) container = CID_HEADGEAR; else if (miscItem->implant) container = CID_IMPLANT; else container = CID_BACKPACK; if (weight > maxWeight * WEIGHT_FACTOR || tuNeed > maxTU || (itemFd && itemFd->time > maxTU)) continue; tryAddToInventory(inv, &item, &this->csi->ids[container]); } } } } } }
/** * @brief Pack a weapon, possibly with some ammo * @param[in] chr The character that will get the weapon * @param[in] weapon The weapon type index in gi.csi->ods * @param[in] ed The equipment for debug messages * @param[in] missedPrimary if actor didn't get primary weapon, this is 0-100 number to increase ammo number. * @param[in] maxWeight The max weight this actor is allowed to carry. * @sa isLoadableInWeapon() */ int InventoryInterface::PackAmmoAndWeapon (character_t* const chr, const objDef_t* weapon, int missedPrimary, const equipDef_t* ed, int maxWeight) { assert(!weapon->isArmour()); Item item(weapon); const objDef_t* ammo = nullptr; if (weapon->oneshot) { /* The weapon provides its own ammo (i.e. it is charged or loaded in the base.) */ item.setAmmoLeft(weapon->ammo); item.setAmmoDef(weapon); Com_DPrintf(DEBUG_SHARED, "PackAmmoAndWeapon: oneshot weapon '%s' in equipment '%s' (%s).\n", weapon->id, ed->id, invName); } else if (!weapon->isReloadable()) { item.setAmmoDef(weapon); /* no ammo needed, so fire definitions are in item */ } else { /* find some suitable ammo for the weapon (we will have at least one if there are ammos for this * weapon in equipment definition) */ int totalAvailableAmmo = 0; for (int i = 0; i < csi->numODs; i++) { const objDef_t* obj = INVSH_GetItemByIDX(i); if (ed->numItems[i] && obj->isLoadableInWeapon(weapon)) { totalAvailableAmmo++; } } if (totalAvailableAmmo) { int randNumber = rand() % totalAvailableAmmo; for (int i = 0; i < csi->numODs; i++) { const objDef_t* obj = INVSH_GetItemByIDX(i); if (ed->numItems[i] && obj->isLoadableInWeapon(weapon)) { randNumber--; if (randNumber < 0) { ammo = obj; break; } } } } if (!ammo) { Com_DPrintf(DEBUG_SHARED, "PackAmmoAndWeapon: no ammo for sidearm or primary weapon '%s' in equipment '%s' (%s).\n", weapon->id, ed->id, invName); return 0; } /* load ammo */ item.setAmmoLeft(weapon->ammo); item.setAmmoDef(ammo); } if (!item.ammoDef()) { Com_Printf("PackAmmoAndWeapon: no ammo for sidearm or primary weapon '%s' in equipment '%s' (%s).\n", weapon->id, ed->id, invName); return 0; } Inventory* const inv = &chr->inv; const int speed = chr->score.skills[ABILITY_SPEED]; int tuNeed = 0; float weight = GetInventoryState(inv, tuNeed) + item.getWeight(); int maxTU = GET_TU(speed, GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER])); if (weight > maxWeight * WEIGHT_FACTOR || tuNeed > maxTU) { Com_DPrintf(DEBUG_SHARED, "PackAmmoAndWeapon: weapon too heavy: '%s' in equipment '%s' (%s).\n", weapon->id, ed->id, invName); return 0; } /* are we going to allow trying the left hand? */ const bool allowLeft = !(inv->getContainer2(CID_RIGHT) && inv->getContainer2(CID_RIGHT)->def()->fireTwoHanded); int ammoMult = 1; /* now try to pack the weapon */ bool packed = tryAddToInventory(inv, &item, &csi->ids[CID_RIGHT]); if (packed) ammoMult = 3; if (!packed && allowLeft) packed = tryAddToInventory(inv, &item, &csi->ids[CID_LEFT]); if (!packed) packed = tryAddToInventory(inv, &item, &csi->ids[CID_BELT]); if (!packed) packed = tryAddToInventory(inv, &item, &csi->ids[CID_HOLSTER]); if (!packed) packed = tryAddToInventory(inv, &item, &csi->ids[CID_BACKPACK]); if (!packed) return 0; /* pack some more ammo in the backpack */ if (ammo) { int numpacked = 0; /* how many clips? */ int num = (1 + ed->numItems[ammo->idx]) * (float) (1.0f + missedPrimary / 100.0); /* pack some ammo */ while (num--) { weight = GetInventoryState(inv, tuNeed) + item.getWeight(); maxTU = GET_TU(speed, GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER])); Item mun(ammo); /* ammo to backpack; belt is for knives and grenades */ if (weight <= maxWeight * WEIGHT_FACTOR && tuNeed <= maxTU) numpacked += tryAddToInventory(inv, &mun, &csi->ids[CID_BACKPACK]); /* no problem if no space left; one ammo already loaded */ if (numpacked > ammoMult || numpacked * weapon->ammo > 11) break; } } return true; }