/** * @brief Add ammo. * @param ent * @param weapon * @param count * @param fillClip * @return whether any ammo was added */ int Add_Ammo(gentity_t *ent, weapon_t weapon, int count, qboolean fillClip) { weapon_t ammoweap = GetWeaponTableData(weapon)->ammoIndex; int maxammo = BG_MaxAmmoForWeapon(ammoweap, ent->client->sess.skill); int originalCount = ent->client->ps.ammo[ammoweap]; if (GetWeaponTableData(ammoweap)->isGrenade || ammoweap == WP_DYNAMITE || ammoweap == WP_SATCHEL_DET) // make sure if he picks it up that he get's the "launcher" too { COM_BitSet(ent->client->ps.weapons, ammoweap); fillClip = qtrue; // always filter into the "clip" } if (fillClip) { Fill_Clip(&ent->client->ps, weapon); } if (GetWeaponTableData(ammoweap)->isPanzer || ammoweap == WP_FLAMETHROWER) { ent->client->ps.ammoclip[ammoweap] += count; if (ent->client->ps.ammoclip[ammoweap] > maxammo) { ent->client->ps.ammoclip[ammoweap] = maxammo; // - ent->client->ps.ammoclip[BG_FindClipForWeapon(weapon)]; } } else { ent->client->ps.ammo[ammoweap] += count; if (ent->client->ps.ammo[ammoweap] > maxammo) { ent->client->ps.ammo[ammoweap] = maxammo; // - ent->client->ps.ammoclip[BG_FindClipForWeapon(weapon)]; } } if (count >= 999) // 'really, give /all/' { ent->client->ps.ammo[ammoweap] = count; } return (ent->client->ps.ammo[ammoweap] > originalCount); }
/** * @brief Add the specified ammount of ammo into the clip. * @param[in,out] ps which player * @param[in] weapon to add ammo for * @param[in] ammomove amount to add. 0 means fill the clip if possible * @param[in] outOfReserve amount to be added out of reserve * @return qboolean whether ammo was added to the clip. */ int AddToClip(playerState_t *ps, weapon_t weapon, int ammomove, int outOfReserve) { int inclip, maxclip; weapon_t ammoweap = GetWeaponTableData(weapon)->ammoIndex; if (!IS_VALID_WEAPON(weapon)) { return qfalse; } inclip = ps->ammoclip[GetWeaponTableData(weapon)->clipIndex]; maxclip = GetWeaponTableData(weapon)->maxClip; if (!ammomove) // amount to add to the clip not specified { ammomove = maxclip - inclip; // max amount that can be moved into the clip } else if (ammomove > maxclip - inclip) { ammomove = maxclip - inclip; } if (outOfReserve) { // cap move amount if it's more than you've got in reserve if (ammomove > ps->ammo[ammoweap]) { ammomove = ps->ammo[ammoweap]; } } if (ammomove) { if (outOfReserve) { ps->ammo[ammoweap] -= ammomove; } ps->ammoclip[GetWeaponTableData(weapon)->clipIndex] += ammomove; return qtrue; } return qfalse; }
/** * @brief Check if a weapon can be picked up. * @param[in] weapon * @param[in] ent * @return */ qboolean G_CanPickupWeapon(weapon_t weapon, gentity_t *ent) { // prevent picking up while reloading if (ent->client->ps.weaponstate == WEAPON_RELOADING) { return qfalse; } // prevent picking up when overheating if (ent->client->ps.weaponTime > 0) { return qfalse; } // get an equivalent weapon if the cleint team is different of the weapon team, if not keep the current if (ent->client->sess.sessionTeam != GetWeaponTableData(weapon)->team && GetWeaponTableData(weapon)->weapEquiv) { weapon = GetWeaponTableData(weapon)->weapEquiv; } return BG_WeaponIsPrimaryForClassAndTeam(ent->client->sess.playerType, ent->client->sess.sessionTeam, weapon); }
/** * @brief G_PrintAccuracyLog * @param[in] ent */ void G_PrintAccuracyLog(gentity_t *ent) { int i; char buffer[2048]; Q_strncpyz(buffer, "WeaponStats", 2048); for (i = WP_KNIFE; i < WP_NUM_WEAPONS; i++) { if (GetWeaponTableData(i)->indexWeaponStat == WS_MAX) { continue; } Q_strcat(buffer, 2048, va(" %i %i %i", ent->client->pers.playerStats.weaponStats[i].kills, ent->client->pers.playerStats.weaponStats[i].killedby, ent->client->pers.playerStats.weaponStats[i].teamkills)); } Q_strcat(buffer, 2048, va(" %i", ent->client->pers.playerStats.selfkills)); for (i = 0; i < HR_NUM_HITREGIONS; i++) { Q_strcat(buffer, 2048, va(" %i", ent->client->pers.playerStats.hitRegions[i])); } Q_strcat(buffer, 2048, va(" %i", 6 /*level.numOidTriggers*/)); for (i = 0; i < 6 /*level.numOidTriggers*/; i++) { Q_strcat(buffer, 2048, va(" %i", ent->client->pers.playerStats.objectiveStats[i])); Q_strcat(buffer, 2048, va(" %i", ent->client->sess.sessionTeam == TEAM_AXIS ? level.objectiveStatsAxis[i] : level.objectiveStatsAllies[i])); } trap_SendServerCommand(ent - g_entities, buffer); }
/** * @brief Local func to actual do skill upgrade, used by both MP skill system, and SP scripted skill system * @param[in,out] ent * @param[in] skill */ void G_UpgradeSkill(gentity_t *ent, skillType_t skill) { int i; #ifdef FEATURE_LUA // *LUA* API callbacks if (G_LuaHook_UpgradeSkill(g_entities - ent, skill)) { return; } #endif // See if this is the first time we've reached this skill level for (i = 0; i < SK_NUM_SKILLS; i++) { if (i == skill) { continue; } if (ent->client->sess.skill[skill] <= ent->client->sess.skill[i]) { break; } } G_DebugAddSkillLevel(ent, skill); #ifdef FEATURE_RATING if (g_skillRating.integer) { ent->client->sess.rank = (int)(MAX(ent->client->sess.mu - 3 * ent->client->sess.sigma, 0.f) / (2 * MU) * NUM_EXPERIENCE_LEVELS); if (ent->client->sess.rank > 10) { ent->client->sess.rank = 10; } } else { #endif if (i == SK_NUM_SKILLS) { // increase rank ent->client->sess.rank++; } if (ent->client->sess.rank >= 4) { int cnt = 0; // count the number of maxed out skills for (i = 0; i < SK_NUM_SKILLS; i++) { if (ent->client->sess.skill[i] >= 4) { cnt++; } } ent->client->sess.rank = cnt + 3; if (ent->client->sess.rank > 10) { ent->client->sess.rank = 10; } } #ifdef FEATURE_RATING } #endif ClientUserinfoChanged(ent - g_entities); // Give em rightaway if (skill == SK_BATTLE_SENSE && ent->client->sess.skill[skill] == 1) { AddWeaponToPlayer(ent->client, WP_BINOCULARS, 1, 0, qfalse); ent->client->ps.stats[STAT_KEYS] |= (1 << INV_BINOCS); } else if (skill == SK_FIRST_AID && ent->client->sess.playerType == PC_MEDIC && ent->client->sess.skill[skill] == 4) { AddWeaponToPlayer(ent->client, WP_MEDIC_ADRENALINE, ent->client->ps.ammo[GetWeaponTableData(WP_MEDIC_ADRENALINE)->ammoIndex], ent->client->ps.ammoclip[GetWeaponTableData(WP_MEDIC_ADRENALINE)->clipIndex], qfalse); } }
/** * @brief Pick a weapon up. * @param[in,out] ent * @param[in,out] other * @return */ int Pickup_Weapon(gentity_t *ent, gentity_t *other) { int quantity; qboolean alreadyHave = qfalse; // magic ammo for any two-handed weapon if (ent->item->giWeapon == WP_AMMO) { AddMagicAmmo(other, ent->count); if (ent->parent && ent->parent->client) { other->client->pers.lastammo_client = ent->parent->s.clientNum; } // if field ops isn't giving ammo to self or the enemy, give him some props if (ent->parent && (ent->parent->client != other->client)) { if (ent->parent && ent->parent->client && other->client->sess.sessionTeam == ent->parent->client->sess.sessionTeam) { G_AddSkillPoints(ent->parent, SK_SIGNALS, 1.f); G_DebugAddSkillPoints(ent->parent, SK_SIGNALS, 1.f, "ammo pack picked up"); #ifdef FEATURE_OMNIBOT //omni-bot event if (ent->parent) { Bot_Event_RecievedAmmo(other - g_entities, ent->parent); } #endif // extracted code originally here into AddMagicAmmo // add 1 clip of magic ammo for any two-handed weapon } return RESPAWN_NEVER; } } quantity = ent->count; // check if player already had the weapon alreadyHave = COM_BitCheck(other->client->ps.weapons, ent->item->giWeapon); // prevents drop/pickup weapon "quick reload" exploit if (alreadyHave) { Add_Ammo(other, ent->item->giWeapon, quantity, qfalse); // secondary weapon ammo if (ent->delay != 0.f) { Add_Ammo(other, GetWeaponTableData(ent->item->giWeapon)->weapAlts, ent->delay, qfalse); } } else { if (level.time - other->client->dropWeaponTime < 1000) { return 0; } // don't pick up when MG or mortar is set if (GetWeaponTableData(other->client->ps.weapon)->isSetWeapon) { return 0; } // see if we can pick it up if (G_CanPickupWeapon(ent->item->giWeapon, other)) { weapon_t primaryWeapon; if (other->client->sess.playerType == PC_SOLDIER && other->client->sess.skill[SK_HEAVY_WEAPONS] >= 4) { primaryWeapon = G_GetPrimaryWeaponForClientSoldier(ent->item->giWeapon, other->client); } else { primaryWeapon = G_GetPrimaryWeaponForClient(other->client); } // added parens around ambiguous && if (primaryWeapon) { // drop our primary weapon G_DropWeapon(other, primaryWeapon); // now pickup the other one other->client->dropWeaponTime = level.time; // add the weapon COM_BitSet(other->client->ps.weapons, ent->item->giWeapon); // fixup mauser/sniper issues if (GetWeaponTableData(ent->item->giWeapon)->weapAlts != WP_NONE) { weapon_t weapAlts = GetWeaponTableData(ent->item->giWeapon)->weapAlts; if (GetWeaponTableData(weapAlts)->isRiflenade || GetWeaponTableData(weapAlts)->isScoped || GetWeaponTableData(weapAlts)->isSetWeapon) { COM_BitSet(other->client->ps.weapons, weapAlts); } } other->client->ps.ammoclip[GetWeaponTableData(ent->item->giWeapon)->clipIndex] = 0; other->client->ps.ammo[GetWeaponTableData(ent->item->giWeapon)->ammoIndex] = 0; if (GetWeaponTableData(ent->item->giWeapon)->isMortar) { other->client->ps.ammo[GetWeaponTableData(ent->item->giWeapon)->clipIndex] = quantity; // secondary weapon ammo if (ent->delay != 0.f) { Add_Ammo(other, GetWeaponTableData(ent->item->giWeapon)->weapAlts, ent->delay, qfalse); } } else { other->client->ps.ammoclip[GetWeaponTableData(ent->item->giWeapon)->clipIndex] = quantity; // secondary weapon ammo if (ent->delay != 0.f) { other->client->ps.ammo[GetWeaponTableData(ent->item->giWeapon)->weapAlts] = ent->delay; } } } } else { return 0; } } #ifdef FEATURE_OMNIBOT Bot_Event_AddWeapon(other->client->ps.clientNum, Bot_WeaponGameToBot(ent->item->giWeapon)); #endif return RESPAWN_NEVER; }
/** * @brief Drop Weapon * @param[in] ent * @param[in] weapon */ void G_DropWeapon(gentity_t *ent, weapon_t weapon) { vec3_t angles, velocity, org, offset, mins, maxs; gclient_t *client = ent->client; gentity_t *ent2; gitem_t *item; trace_t tr; if (!IS_VALID_WEAPON(weapon)) { return; } item = BG_FindItemForWeapon(weapon); VectorCopy(client->ps.viewangles, angles); // clamp pitch if (angles[PITCH] < -30) { angles[PITCH] = -30; } else if (angles[PITCH] > 30) { angles[PITCH] = 30; } AngleVectors(angles, velocity, NULL, NULL); VectorScale(velocity, 64, offset); offset[2] += client->ps.viewheight / 2.f; VectorScale(velocity, 75, velocity); velocity[2] += 50 + random() * 35; VectorAdd(client->ps.origin, offset, org); VectorSet(mins, -ITEM_RADIUS, -ITEM_RADIUS, 0); VectorSet(maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS); trap_Trace(&tr, client->ps.origin, mins, maxs, org, ent->s.number, MASK_SOLID); VectorCopy(tr.endpos, org); ent2 = LaunchItem(item, org, velocity, client->ps.clientNum); COM_BitClear(client->ps.weapons, weapon); if (GetWeaponTableData(weapon)->weapAlts != WP_NONE) { weapon_t weapAlts = GetWeaponTableData(weapon)->weapAlts; if (GetWeaponTableData(weapAlts)->isRiflenade || GetWeaponTableData(weapAlts)->isScoped || GetWeaponTableData(weapAlts)->isSetWeapon) { COM_BitClear(client->ps.weapons, weapAlts); } } // Clear out empty weapon, change to next best weapon G_AddEvent(ent, EV_WEAPONSWITCHED, 0); if (weapon == client->ps.weapon) { client->ps.weapon = 0; } if (GetWeaponTableData(weapon)->isMortarSet) { ent2->count = client->ps.ammo[GetWeaponTableData(weapon)->ammoIndex] + client->ps.ammoclip[GetWeaponTableData(weapon)->clipIndex]; } else { ent2->count = client->ps.ammoclip[GetWeaponTableData(weapon)->clipIndex]; } if (weapon == WP_KAR98 || weapon == WP_CARBINE) { ent2->delay = client->ps.ammo[GetWeaponTableData(GetWeaponTableData(weapon)->weapAlts)->ammoIndex]; } else { ent2->delay = 0; } // ent2->item->quantity = client->ps.ammoclip[BG_FindClipForWeapon(weapon)]; // um, modifying an item is not a good idea client->ps.ammoclip[GetWeaponTableData(weapon)->clipIndex] = 0; #ifdef FEATURE_OMNIBOT Bot_Event_RemoveWeapon(client->ps.clientNum, Bot_WeaponGameToBot(weapon)); #endif }
/** * @brief CG_TransitionPlayerState * @param[in] ps * @param[in] ops */ void CG_TransitionPlayerState(playerState_t *ps, playerState_t *ops) { #ifdef FEATURE_MULTIVIEW // MV client handling if (cg.mvTotalClients > 0) { if (ps->clientNum != ops->clientNum) { cg.thisFrameTeleport = qtrue; // clear voicechat cg.predictedPlayerEntity.voiceChatSpriteTime = 0; // CHECKME: should we do this here? cg_entities[ps->clientNum].voiceChatSpriteTime = 0; *ops = *ps; } CG_CheckLocalSounds(ps, ops); return; } #endif // check for changing follow mode if (ps->clientNum != ops->clientNum) { cg.thisFrameTeleport = qtrue; // clear voicechat cg.predictedPlayerEntity.voiceChatSpriteTime = 0; cg_entities[ps->clientNum].voiceChatSpriteTime = 0; // make sure we don't get any unwanted transition effects *ops = *ps; // After Limbo, make sure and do a CG_Respawn if (ps->clientNum == cg.clientNum) { ops->persistant[PERS_SPAWN_COUNT]--; } } if (ps->eFlags & EF_FIRING) { cg.lastFiredWeaponTime = 0; cg.weaponFireTime += cg.frametime; } else { if (cg.weaponFireTime > 500 /*&& cg.weaponFireTime*/) { cg.lastFiredWeaponTime = cg.time; } cg.weaponFireTime = 0; } // damage events (player is getting wounded) if (ps->damageEvent != ops->damageEvent && ps->damageCount) { CG_DamageFeedback(ps->damageYaw, ps->damagePitch, ps->damageCount); } // respawning if (ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT]) { CG_Respawn(ps->persistant[PERS_REVIVE_COUNT] != ops->persistant[PERS_REVIVE_COUNT] ? qtrue : qfalse); } if (cg.mapRestart) { CG_Respawn(qfalse); cg.mapRestart = qfalse; } if (cg.snap->ps.pm_type != PM_INTERMISSION && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR) { CG_CheckLocalSounds(ps, ops); } if (ps->eFlags & EF_PRONE_MOVING) { if (ps->weapon == WP_BINOCULARS) { if (ps->eFlags & EF_ZOOMING) { trap_SendConsoleCommand("-zoom\n"); } } else if (GetWeaponTableData(ps->weapon)->type & WEAPON_TYPE_SCOPED) { CG_FinishWeaponChange(ps->weapon, GetWeaponTableData(ps->weapon)->weapAlts); } if (!(ops->eFlags & EF_PRONE_MOVING)) { // this screws up auto-switching when dynamite planted or grenade thrown/out of ammo //CG_FinishWeaponChange( cg.weaponSelect, ps->nextWeapon ); cg.proneMovingTime = cg.time; } } else if (ops->eFlags & EF_PRONE_MOVING) { cg.proneMovingTime = -cg.time; } if (!(ps->eFlags & EF_PRONE) && (ops->eFlags & EF_PRONE)) { if (CHECKBITWISE(GetWeaponTableData(cg.weaponSelect)->type, WEAPON_TYPE_MG | WEAPON_TYPE_SET)) { CG_FinishWeaponChange(cg.weaponSelect, ps->nextWeapon); } } // don't let players run with rifles -- speed 80 == crouch, 128 == walk, 256 == run until player start to don't run if ((GetWeaponTableData(ps->weapon)->type & WEAPON_TYPE_SCOPED) && VectorLength(ps->velocity) > 127) { CG_FinishWeaponChange(ps->weapon, GetWeaponTableData(ps->weapon)->weapAlts); } // run events CG_CheckPlayerstateEvents(ps, ops); // smooth the ducking viewheight change if (ps->viewheight != ops->viewheight) { cg.duckChange = ps->viewheight - ops->viewheight; cg.duckTime = cg.time; } }
/** * @brief A respawn happened this snapshot * @param[in] revived */ void CG_Respawn(qboolean revived) { static int oldTeam = -1; static int oldCls = -1; cg.serverRespawning = qfalse; // just in case // no error decay on player movement cg.thisFrameTeleport = qtrue; // need to reset client-side weapon animations cg.predictedPlayerState.weapAnim = ((cg.predictedPlayerState.weapAnim & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | GetWeaponTableData(cg.snap->ps.weapon)->idleAnim; // reset weapon animations cg.predictedPlayerState.weaponstate = WEAPON_READY; // hmm, set this? what to? // display weapons available cg.weaponSelectTime = cg.time; cg.cursorHintIcon = 0; cg.cursorHintTime = 0; // select the weapon the server says we are using cg.weaponSelect = cg.snap->ps.weapon; // clear even more things on respawn cg.zoomedBinoc = qfalse; cg.zoomed = qfalse; cg.zoomTime = 0; cg.zoomval = 0; trap_SendConsoleCommand("-zoom\n"); cg.binocZoomTime = 0; // ensure scoped weapons are reset after revive if (revived) { if (GetWeaponTableData(cg.snap->ps.weapon)->type & WEAPON_TYPE_SCOPED) { CG_FinishWeaponChange(cg.snap->ps.weapon, GetWeaponTableData(cg.snap->ps.weapon)->weapAlts); } } // clear pmext Com_Memset(&cg.pmext, 0, sizeof(cg.pmext)); cg.pmext.bAutoReload = (qboolean)(cg_autoReload.integer > 0); cg.pmext.sprintTime = SPRINTTIME; if (!revived) { cgs.limboLoadoutSelected = qfalse; // reset switch back weapon cg.switchbackWeapon = WP_NONE; } // Saves the state of sidearm (riflenade weapon is considered as one too) // Puts the silencer on if class is COVERTOPS // Puts riflenade on if current weapon is riflenade weapon if (cg.predictedPlayerState.stats[STAT_PLAYER_CLASS] == PC_COVERTOPS) { cg.pmext.silencedSideArm = 1; } else if (GetWeaponTableData(cg.predictedPlayerState.weapon)->type & WEAPON_TYPE_RIFLENADE) { cg.pmext.silencedSideArm = 2; } cg.proneMovingTime = 0; // reset fog to world fog (if present) trap_R_SetFog(FOG_CMD_SWITCHFOG, FOG_MAP, 20, 0, 0, 0, 0); // try to exec a cfg file if it is found if (!revived) { if ((cgs.clientinfo[cg.clientNum].team == TEAM_AXIS || cgs.clientinfo[cg.clientNum].team == TEAM_ALLIES) && (cgs.clientinfo[cg.clientNum].cls != oldCls)) { CG_execFile(va("autoexec_%s", BG_ClassnameForNumberFilename(cgs.clientinfo[cg.clientNum].cls))); oldCls = cgs.clientinfo[cg.clientNum].cls; } if (cgs.clientinfo[cg.clientNum].team != oldTeam) { CG_execFile(va("autoexec_%s", BG_TeamnameForNumber(cgs.clientinfo[cg.clientNum].team))); oldTeam = cgs.clientinfo[cg.clientNum].team; } } }