void G_CheckDeathOrKnockout (edict_t *target, edict_t *attacker, const fireDef_t *fd, int damage) { /* Check death/knockout. */ if (target->HP == 0 || target->HP <= target->STUN) { G_SendStats(target); if (G_ActorDieOrStun(target, attacker)) { G_PrintActorStats(target, attacker, fd); /* apply morale changes */ if (mor_panic->integer) G_Morale(ML_DEATH, target, attacker, damage); /* Update number of killed/stunned actors for this attacker. */ G_UpdateCharacterBodycount(attacker, fd, target); } } else { target->chr.minHP = std::min(target->chr.minHP, target->HP); if (damage > 0) { if (mor_panic->integer) G_Morale(ML_WOUND, target, attacker, damage); } else { /* medikit, etc. */ const int hp = GET_HP(target->chr.score.skills[ABILITY_POWER]); if (target->HP > hp) { target->HP = std::min(std::max(hp, 0), target->chr.maxHP); } } G_SendStats(target); } }
void G_CheckDeathOrKnockout (Edict* target, Edict* attacker, const fireDef_t* fd, int damage) { /* Sanity check */ target->HP = std::min(std::max(target->HP, 0), target->chr.maxHP); /* Check death/knockout. */ if (target->HP == 0 || target->HP <= target->STUN) { G_SendStats(*target); if (G_ActorDieOrStun(target, attacker)) { G_PrintActorStats(target, attacker, fd); /* apply morale changes */ if (mor_panic->integer) G_Morale(ML_DEATH, target, attacker, damage); /* Update number of killed/stunned actors for this attacker. */ G_UpdateCharacterBodycount(attacker, fd, target); } } else { target->chr.minHP = std::min(target->chr.minHP, target->HP); if (damage > 0) { if (mor_panic->integer) G_Morale(ML_WOUND, target, attacker, damage); } G_SendStats(*target); } }
/** * @brief Sends the actual actor turn event over the netchannel */ static void G_ClientTurn (player_t * player, edict_t* ent, dvec_t dvec) { const int dir = getDVdir(dvec); /* check if action is possible */ if (!G_ActionCheckForCurrentTeam(player, ent, TU_TURN)) return; /* check if we're already facing that direction */ if (ent->dir == dir) return; /* do the turn */ G_ActorDoTurn(ent, dir); G_ActorUseTU(ent, TU_TURN); /* send the turn */ G_EventActorTurn(ent); /* send the new TUs */ G_SendStats(ent); /* end the event */ G_EventEnd(); }
/** * @brief Network function to update the time units (TUs) for each team-member. * @param[in] team The index of the team to update the values for. * @sa G_SendStats */ void G_GiveTimeUnits (int team) { edict_t *ent = NULL; while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, team))) { G_ActorGiveTimeUnits(ent); G_SendStats(ent); } }
/** * @brief Applies morale behaviour on actors * @note only called when mor_panic is not zero * @sa G_MoralePanic * @sa G_MoraleRage * @sa G_MoraleStopRage * @sa G_MoraleStopPanic */ void G_MoraleBehaviour (int team) { edict_t *ent = NULL; int newMorale; while ((ent = G_EdictsGetNextInUse(ent))) { /* this only applies to ET_ACTOR but not to ET_ACTOR2x2 */ if (ent->type == ET_ACTOR && ent->team == team && !G_IsDead(ent)) { /* civilians have a 1:1 chance to randomly run away in multiplayer */ if (sv_maxclients->integer >= 2 && level.activeTeam == TEAM_CIVILIAN && 0.5 > frand()) G_MoralePanic(ent, qfalse); /* multiplayer needs enabled sv_enablemorale */ /* singleplayer has this in every case */ if (G_IsMoraleEnabled()) { /* if panic, determine what kind of panic happens: */ if (ent->morale <= mor_panic->value && !G_IsPaniced(ent) && !G_IsRaged(ent)) { qboolean sanity; if ((float) ent->morale / mor_panic->value > (m_sanity->value * frand())) sanity = qtrue; else sanity = qfalse; if ((float) ent->morale / mor_panic->value > (m_rage->value * frand())) G_MoralePanic(ent, sanity); else G_MoraleRage(ent, sanity); /* if shaken, well .. be shaken; */ } else if (ent->morale <= mor_shaken->value && !G_IsPaniced(ent) && !G_IsRaged(ent)) { /* shaken is later reset along with reaction fire */ G_SetShaken(ent); G_SetState(ent, STATE_REACTION); G_EventSendState(G_VisToPM(ent->visflags), ent); G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("%s is currently shaken.\n"), ent->chr.name); } else { if (G_IsPaniced(ent)) G_MoraleStopPanic(ent); else if (G_IsRaged(ent)) G_MoraleStopRage(ent); } } G_ActorSetMaxs(ent); /* morale-regeneration, capped at max: */ newMorale = ent->morale + MORALE_RANDOM(mor_regeneration->value); if (newMorale > GET_MORALE(ent->chr.score.skills[ABILITY_MIND])) ent->morale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]); else ent->morale = newMorale; /* send phys data and state: */ G_SendStats(ent); gi.EndEvents(); } } }
/** * @brief Applies morale behaviour on actors * @note only called when mor_panic is not zero * @sa G_MoralePanic * @sa G_MoraleRage * @sa G_MoraleStopRage * @sa G_MoraleStopPanic */ void G_MoraleBehaviour (int team) { bool enabled = G_IsMoraleEnabled(team); if (!enabled) return; Edict* ent = nullptr; while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, team)) != nullptr) { /* this only applies to ET_ACTOR but not to ET_ACTOR2x2 */ if (ent->type != ET_ACTOR || CHRSH_IsTeamDefRobot(ent->chr.teamDef)) continue; /* if panic, determine what kind of panic happens: */ if (!G_IsPanicked(ent) && !G_IsRaged(ent)) { if (ent->morale <= mor_panic->integer) { const float ratio = (float) ent->morale / mor_panic->value; const bool sanity = ratio > (m_sanity->value * frand()); if (!sanity) G_SetInsane(ent); if (ratio > (m_rage->value * frand())) G_MoralePanic(ent); else G_MoraleRage(ent); /* if shaken, well .. be shaken; */ } else if (ent->morale <= mor_shaken->integer) { /* shaken is later reset along with reaction fire */ G_SetShaken(ent); G_ClientStateChange(ent->getPlayer(), ent, STATE_REACTION, false); G_EventSendState(G_VisToPM(ent->visflags), *ent); G_ClientPrintf(ent->getPlayer(), PRINT_HUD, _("%s is currently shaken."), ent->chr.name); G_PrintStats("%s is shaken (entnum %i).", ent->chr.name, ent->getIdNum()); } } else { if (G_IsPanicked(ent)) G_MoraleStopPanic(ent); else if (G_IsRaged(ent)) G_MoraleStopRage(ent); } G_ActorSetMaxs(ent); /* morale-regeneration, capped at max: */ int newMorale = ent->morale + MORALE_RANDOM(mor_regeneration->value); const int maxMorale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]); if (newMorale > maxMorale) ent->morale = maxMorale; else ent->morale = newMorale; /* send phys data and state: */ G_SendStats(*ent); } }
/** * @brief After an actor changed his state, he might get visible for other * players. Check the vis here and send the state change to the clients that * are seeing him already. * @param ent The actor edict */ static void G_ClientStateChangeUpdate (edict_t *ent) { /* Send the state change. */ G_EventSendState(G_VisToPM(ent->visflags), ent); /* Check if the player appears/perishes, seen from other teams. */ G_CheckVis(ent); /* Calc new vis for this player. */ G_CheckVisTeamAll(ent->team, 0, ent); /* Send the new TUs. */ G_SendStats(ent); /* End the event. */ G_EventEnd(); }
void G_ActorCheckRevitalise (Edict* ent) { if (G_IsStunned(ent) && ent->STUN < ent->HP) { /* check that we could move after we stood up */ Edict* otherActor = nullptr; while ((otherActor = G_EdictsGetNextInUse(otherActor))) { if (!VectorCompare(ent->pos, otherActor->pos)) continue; if (G_IsBlockingMovementActor(otherActor)) return; } G_ActorRevitalise(ent); G_EventActorRevitalise(*ent); G_SendStats(*ent); } }
/** * @brief This function 'uses' the edict. E.g. it opens the door when the player wants it to open * @sa PA_USE_DOOR * @param[in] player The player is trying to activate the door * @param[in,out] actor The actor the player is using to activate the entity * @param[in,out] edict The entity that is to be used * @todo Do we have to change the trigger position here, too? I don't think this is really needed. * @sa CL_ActorUse * @sa G_UseEdict */ bool G_ClientUseEdict (const player_t *player, edict_t *actor, edict_t *edict) { /* check whether the actor has sufficient TUs to 'use' this edicts */ if (!G_ActionCheckForCurrentTeam(player, actor, edict->TU)) return false; if (!G_UseEdict(edict, actor)) return false; /* using a group of edicts only costs TUs once (for the master) */ G_ActorUseTU(actor, edict->TU); /* send the new TUs */ G_SendStats(actor); G_EventEnd(); return true; }
/** * @brief Generates the client events that are send over the netchannel to move an actor * @param[in] player Player who is moving an actor * @param[in] visTeam The team to check the visibility for - if this is 0 we build the forbidden list * above all edicts - for the human controlled actors this would mean that clicking to a grid * position that is not reachable because an invisible actor is standing there would not result in * a single step - as the movement is aborted before. For AI movement this is in general @c 0 - but * not if they e.g. hide. * @param[in] ent Edict to move * @param[in] to The grid position to walk to * @sa CL_ActorStartMove * @sa PA_MOVE */ void G_ClientMove (const player_t * player, int visTeam, edict_t* ent, const pos3_t to) { int status, initTU; dvec_t dvtab[MAX_DVTAB]; int dir; byte numdv, length; pos3_t pos; float div; int oldState; int oldHP; bool autoCrouchRequired = false; byte crouchingState; if (VectorCompare(ent->pos, to)) return; /* check if action is possible */ if (!G_ActionCheckForCurrentTeam(player, ent, TU_MOVE_STRAIGHT)) return; crouchingState = G_IsCrouched(ent) ? 1 : 0; oldState = oldHP = 0; /* calculate move table */ G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); /* length of ROUTING_NOT_REACHABLE means not reachable */ if (length && length >= ROUTING_NOT_REACHABLE) return; /* Autostand: check if the actor is crouched and player wants autostanding...*/ if (crouchingState && player->autostand) { /* ...and if this is a long walk... */ if (SHOULD_USE_AUTOSTAND(length)) { /* ...make them stand first. If the player really wants them to walk a long * way crouched, he can move the actor in several stages. * Uses the threshold at which standing, moving and crouching again takes * fewer TU than just crawling while crouched. */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); /* change to stand state */ crouchingState = G_IsCrouched(ent) ? 1 : 0; if (!crouchingState) { G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); autoCrouchRequired = true; } } } /* this let the footstep sounds play even over network */ ent->think = G_PhysicsStep; ent->nextthink = level.time; /* assemble dvec-encoded move data */ VectorCopy(to, pos); initTU = ent->TU; numdv = G_FillDirectionTable(dvtab, lengthof(dvtab), crouchingState, pos); /* make sure to end any other pending events - we rely on EV_ACTOR_MOVE not being active anymore */ gi.EndEvents(); /* everything ok, found valid route? */ if (VectorCompare(pos, ent->pos)) { byte* stepAmount = NULL; int usedTUs = 0; /* no floor inventory at this point */ FLOOR(ent) = NULL; while (numdv > 0) { /* A flag to see if we needed to change crouch state */ int crouchFlag; const byte oldDir = ent->dir; int dvec; /* get next dvec */ numdv--; dvec = dvtab[numdv]; /* This is the direction to make the step into */ dir = getDVdir(dvec); /* turn around first */ status = G_ActorDoTurn(ent, dir); if (status & VIS_STOP) { autoCrouchRequired = false; if (ent->moveinfo.steps == 0) usedTUs += TU_TURN; break; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; /* if something appears on our route that didn't trigger a VIS_STOP, we have to * send the turn event if this is our first step */ if (oldDir != ent->dir && ent->moveinfo.steps == 0) { G_EventActorTurn(ent); usedTUs += TU_TURN; } break; } /* decrease TUs */ div = gi.GetTUsForDirection(dir, G_IsCrouched(ent)); if ((int) (usedTUs + div) > ent->TU) break; usedTUs += div; /* This is now a flag to indicate a change in crouching - we need this for * the stop in mid move call(s), because we need the updated entity position */ crouchFlag = 0; /* Calculate the new position after the decrease in TUs, otherwise the game * remembers the false position if the time runs out */ PosAddDV(ent->pos, crouchFlag, dvec); /* slower if crouched */ if (G_IsCrouched(ent)) ent->speed = ACTOR_SPEED_CROUCHED; else ent->speed = ACTOR_SPEED_NORMAL; ent->speed *= g_actorspeed->value; if (crouchFlag == 0) { /* No change in crouch */ edict_t* clientAction; int contentFlags; vec3_t pointTrace; G_EdictCalcOrigin(ent); VectorCopy(ent->origin, pointTrace); pointTrace[2] += PLAYER_MIN; contentFlags = gi.PointContents(pointTrace); /* link it at new position - this must be done for every edict * movement - to let the server know about it. */ gi.LinkEdict(ent); /* Only the PHALANX team has these stats right now. */ if (ent->chr.scoreMission) { float truediv = gi.GetTUsForDirection(dir, 0); /* regardless of crouching ! */ if (G_IsCrouched(ent)) ent->chr.scoreMission->movedCrouched += truediv; else ent->chr.scoreMission->movedNormal += truediv; } /* write the step to the net */ G_WriteStep(ent, &stepAmount, dvec, contentFlags); /* check if player appears/perishes, seen from other teams */ G_CheckVis(ent, true); /* check for anything appearing, seen by "the moving one" */ status = G_CheckVisTeamAll(ent->team, false, ent); /* Set ent->TU because the reaction code relies on ent->TU being accurate. */ G_ActorSetTU(ent, initTU - usedTUs); clientAction = ent->clientAction; oldState = ent->state; oldHP = ent->HP; /* check triggers at new position */ if (G_TouchTriggers(ent)) { if (!clientAction) status |= VIS_STOP; } G_TouchSolids(ent, 10.0f); /* state has changed - maybe we walked on a trigger_hurt */ if (oldState != ent->state) status |= VIS_STOP; else if (oldHP != ent->HP) status |= VIS_STOP; } else if (crouchFlag == 1) { /* Actor is standing */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } else if (crouchFlag == -1) { /* Actor is crouching and should stand up */ G_ClientStateChange(player, ent, STATE_CROUCHED, false); } /* check for reaction fire */ if (G_ReactionFireOnMovement(ent)) { status |= VIS_STOP; autoCrouchRequired = false; } /* check for death */ if (((oldHP != 0 && oldHP != ent->HP) || (oldState != ent->state)) && !G_IsDazed(ent)) { /** @todo Handle dazed via trigger_hurt */ /* maybe this was due to rf - then the G_ActorDie was already called */ if (!G_IsDead(ent)) { G_CheckDeathOrKnockout(ent, NULL, NULL, oldHP - ent->HP); } return; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv - 1)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; break; } /* Restore ent->TU because the movement code relies on it not being modified! */ G_ActorSetTU(ent, initTU); } /* submit the TUs / round down */ if (g_notu != NULL && g_notu->integer) G_ActorSetTU(ent, initTU); else G_ActorSetTU(ent, initTU - usedTUs); G_SendStats(ent); /* end the move */ G_GetFloorItems(ent); gi.EndEvents(); } if (autoCrouchRequired) { /* toggle back to crouched state */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } }
/** * @brief Applies morale changes to actors around a wounded or killed actor. * @note only called when mor_panic is not zero * @param[in] type Type of morale modifier (@sa morale_modifiers) * @param[in] victim An actor being a victim of the attack. * @param[in] attacker An actor being attacker in this attack. * @param[in] param Used to modify morale changes, for G_Damage() it is value of damage. * @sa G_Damage */ static void G_Morale (int type, const edict_t * victim, const edict_t * attacker, int param) { edict_t *ent = NULL; int newMorale; float mod; while ((ent = G_EdictsGetNextInUse(ent))) { /* this only applies to ET_ACTOR but not ET_ACTOR2x2 */ if (ent->type == ET_ACTOR && !G_IsDead(ent) && ent->team != TEAM_CIVILIAN) { switch (type) { case ML_WOUND: case ML_DEATH: /* morale damage depends on the damage */ mod = mob_wound->value * param; /* death hurts morale even more than just damage */ if (type == ML_DEATH) mod += mob_death->value; /* seeing how someone gets shot increases the morale change */ if (ent == victim || (G_FrustumVis(ent, victim->origin) && G_ActorVis(ent->origin, ent, victim, false))) mod *= mof_watching->value; if (attacker != NULL && ent->team == attacker->team) { /* teamkills are considered to be bad form, but won't cause an increased morale boost for the enemy */ /* morale boost isn't equal to morale loss (it's lower, but morale gets regenerated) */ if (victim->team == attacker->team) mod *= mof_teamkill->value; else mod *= mof_enemy->value; } /* seeing a civilian die is more "acceptable" */ if (G_IsCivilian(victim)) mod *= mof_civilian->value; /* if an ally (or in singleplayermode, as human, a civilian) got shot, lower the morale, don't heighten it. */ if (victim->team == ent->team || (G_IsCivilian(victim) && ent->team != TEAM_ALIEN && sv_maxclients->integer == 1)) mod *= -1; if (attacker != NULL) { /* if you stand near to the attacker or the victim, the morale change is higher. */ mod *= mor_default->value + pow(0.5, VectorDist(ent->origin, victim->origin) / mor_distance->value) * mor_victim->value + pow(0.5, VectorDist(ent->origin, attacker->origin) / mor_distance->value) * mor_attacker->value; } else { mod *= mor_default->value + pow(0.5, VectorDist(ent->origin, victim->origin) / mor_distance->value) * mor_victim->value; } /* morale damage depends on the number of living allies */ mod *= (1 - mon_teamfactor->value) + mon_teamfactor->value * (level.num_spawned[victim->team] + 1) / (level.num_alive[victim->team] + 1); /* being hit isn't fun */ if (ent == victim) mod *= mor_pain->value; break; default: gi.DPrintf("Undefined morale modifier type %i\n", type); mod = 0; break; } /* clamp new morale */ /*+0.9 to allow weapons like flamethrowers to inflict panic (typecast rounding) */ newMorale = ent->morale + (int) (MORALE_RANDOM(mod) + 0.9); if (newMorale > GET_MORALE(ent->chr.score.skills[ABILITY_MIND])) ent->morale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]); else if (newMorale < 0) ent->morale = 0; else ent->morale = newMorale; /* send phys data */ G_SendStats(ent); } } }
/** * @brief Deals damage of a give type and amount to a target. * @param[in,out] target What we want to damage. * @param[in] fd The fire definition that defines what type of damage is dealt. * @param[in] damage The value of the damage. * @param[in] attacker The attacker. * @param[in] mock pseudo shooting - only for calculating mock values - NULL for real shots * @param[in] impact impact location - @c NULL for splash damage * @sa G_SplashDamage * @sa G_TakeDamage * @sa G_PrintActorStats */ static void G_Damage (edict_t *target, const fireDef_t *fd, int damage, edict_t *attacker, shot_mock_t *mock, const vec3_t impact) { const bool stunEl = (fd->obj->dmgtype == gi.csi->damStunElectro); const bool stunGas = (fd->obj->dmgtype == gi.csi->damStunGas); const bool shock = (fd->obj->dmgtype == gi.csi->damShock); const bool smoke = (fd->obj->dmgtype == gi.csi->damSmoke); bool isRobot; assert(target); /* Breakables */ if (G_IsBrushModel(target) && G_IsBreakable(target)) { /* Breakables are immune to stun & shock damage. */ if (stunEl || stunGas || shock || mock || smoke) return; if (damage >= target->HP) { /* don't reset the HP value here, this value is used to distinguish * between triggered destroy and a shoot */ assert(target->destroy); target->destroy(target); /* maybe the attacker is seeing something new? */ G_CheckVisTeamAll(attacker->team, 0, attacker); /* check if attacker appears/perishes for any other team */ G_CheckVis(attacker); } else { G_TakeDamage(target, damage); } return; } /* Actors don't die again. */ if (!G_IsLivingActor(target)) return; /* only actors after this point - and they must have a teamdef */ assert(target->chr.teamDef); isRobot = CHRSH_IsTeamDefRobot(target->chr.teamDef); /* Apply armour effects. */ if (damage > 0) { damage = G_ApplyProtection(target, fd->dmgweight, damage); } else if (damage < 0) { /* Robots can't be healed. */ if (isRobot) return; } Com_DPrintf(DEBUG_GAME, " Total damage: %d\n", damage); /* Apply difficulty settings. */ if (sv_maxclients->integer == 1) { if (G_IsAlien(attacker) && !G_IsAlien(target)) damage *= pow(1.18, g_difficulty->value); else if (!G_IsAlien(attacker) && G_IsAlien(target)) damage *= pow(1.18, -g_difficulty->value); } assert(attacker->team >= 0 && attacker->team < MAX_TEAMS); assert(target->team >= 0 && target->team < MAX_TEAMS); if (g_nodamage != NULL && !g_nodamage->integer) { /* hit */ if (mock) { G_UpdateShotMock(mock, attacker, target, damage); } else if (stunEl) { target->STUN += damage; } else if (stunGas) { if (!isRobot) /* Can't stun robots with gas */ target->STUN += damage; } else if (shock) { /* Only do this if it's not one from our own team ... they should have known that there was a flashbang coming. */ if (!isRobot && target->team != attacker->team) { /** @todo there should be a possible protection, too */ /* dazed entity wont reaction fire */ G_RemoveReaction(target); G_ActorReserveTUs(target, 0, target->chr.reservedTus.shot, target->chr.reservedTus.crouch); /* flashbangs kill TUs */ G_ActorSetTU(target, 0); G_SendStats(target); /* entity is dazed */ G_SetDazed(target); G_ClientPrintf(G_PLAYER_FROM_ENT(target), PRINT_HUD, _("Soldier is dazed!\nEnemy used flashbang!")); return; } } else { if (damage < 0) { /* The 'attacker' is healing the target. */ G_TreatActor(target, fd, damage, attacker->team); } else { /* Real damage was dealt. */ G_DamageActor(target, damage, impact); /* Update overall splash damage for stats/score. */ if (!mock && damage > 0 && fd->splrad) /**< Check for >0 and splrad to not count this as direct hit. */ G_UpdateHitScore(attacker, target, fd, damage); } } } if (mock) return; G_CheckDeathOrKnockout(target, attacker, fd, damage); }
/** * @brief Moves an item inside an inventory. Floors are handled special. * @param[in] actor The pointer to the selected/used edict/soldier. * @param[in] fromContType The container (-id) the item should be moved from. * @param[in] fItem The item you want to move. * @param[in] toContType The container (-def) the item should be moved to. * @param[in] tx x position where you want the item to go in the destination container * @param[in] ty y position where you want the item to go in the destination container * @param[in] checkaction Set this to true if you want to check for TUs, otherwise false. * @sa event PA_INVMOVE * @sa AI_ActorThink */ bool G_ActorInvMove (Edict* actor, const invDef_t* fromContType, Item* fItem, const invDef_t* toContType, int tx, int ty, bool checkaction) { Edict* floor; bool newFloor; Item* tc; playermask_t mask; inventory_action_t ia; Item fromItemBackup, toItemBackup; int fx, fy; int originalTU, reservedTU = 0; Player& player = actor->getPlayer(); assert(fItem); assert(fItem->def()); /* Store the location/item of 'from' BEFORE actually moving items with moveInInventory. */ fromItemBackup = *fItem; /* Store the location of 'to' BEFORE actually moving items with moveInInventory * so in case we swap ammo the client can be updated correctly */ tc = actor->chr.inv.getItemAtPos(toContType, tx, ty); if (tc) toItemBackup = *tc; else toItemBackup = *fItem; /* Get first used bit in item. */ fItem->getFirstShapePosition(&fx, &fy); fx += fItem->getX(); fy += fItem->getY(); /* Check if action is possible */ /* TUs are 1 here - but this is only a dummy - the real TU check is done in the inventory functions below */ if (checkaction && !G_ActionCheckForCurrentTeam(player, actor, 1)) return false; if (!actor->chr.inv.canHoldItemWeight(fromContType->id, toContType->id, *fItem, actor->chr.score.skills[ABILITY_POWER])) { G_ClientPrintf(player, PRINT_HUD, _("This soldier can not carry anything else.")); return false; } /* "get floor ready" - searching for existing floor-edict */ floor = G_GetFloorItems(actor); if (toContType->isFloorDef() && !floor) { /* We are moving to the floor, but no existing edict for this floor-tile found -> create new one */ floor = G_SpawnFloor(actor->pos); newFloor = true; } else if (fromContType->isFloorDef() && !floor) { /* We are moving from the floor, but no existing edict for this floor-tile found -> this should never be the case. */ gi.DPrintf("G_ClientInvMove: No source-floor found.\n"); return false; } else { /* There already exists an edict for this floor-tile. */ newFloor = false; } /* search for space */ Item* item2; if (tx == NONE) { item2 = actor->chr.inv.getItemAtPos(fromContType, fItem->getX(), fItem->getY()); if (item2) actor->chr.inv.findSpace(toContType, item2, &tx, &ty, fItem); if (tx == NONE) return false; } /** @todo what if we don't have enough TUs after subtracting the reserved ones? */ /* Because moveInInventory don't know anything about character_t and it updates actor->TU, * we need to save original actor->TU for the sake of checking TU reservations. */ originalTU = actor->TU; reservedTU = G_ActorGetReservedTUs(actor); /* Temporary decrease actor->TU to make moveInInventory do what expected. */ G_ActorUseTU(actor, reservedTU); /* Try to actually move the item and check the return value after restoring valid actor->TU. */ ia = game.i.moveInInventory(&actor->chr.inv, fromContType, fItem, toContType, tx, ty, checkaction ? &actor->TU : nullptr, &item2); /* Now restore the original actor->TU and decrease it for TU used for inventory move. */ G_ActorSetTU(actor, originalTU - (originalTU - reservedTU - actor->TU)); switch (ia) { case IA_NONE: /* No action possible - abort */ return false; case IA_NOTIME: G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - not enough TUs!")); return false; case IA_NORELOAD: G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - weapon already fully loaded with the same ammunition!")); return false; default: /* Continue below. */ break; } /* successful inventory change; remove the item in clients */ if (fromContType->isFloorDef()) { /* We removed an item from the floor - check how the client * needs to be updated. */ assert(!newFloor); if (actor->getFloor()) { /* There is still something on the floor. */ floor->setFloor(actor); /* Delay this if swapping ammo, otherwise the le will be removed in the client before we can add back * the current ammo because removeNextFrame is set in LE_PlaceItem() if the floor le has no items */ if (ia != IA_RELOAD_SWAP) G_EventInventoryDelete(*floor, G_VisToPM(floor->visflags), fromContType->id, fx, fy); } else { /* Floor is empty, remove the edict (from server + client) if we are * not moving to it. */ if (!toContType->isFloorDef()) { G_EventPerish(*floor); G_FreeEdict(floor); } else G_EventInventoryDelete(*floor, G_VisToPM(floor->visflags), fromContType->id, fx, fy); } } else { G_EventInventoryDelete(*actor, G_TeamToPM(actor->team), fromContType->id, fx, fy); } /* send tu's */ G_SendStats(*actor); assert(item2); Item item = *item2; if (ia == IA_RELOAD || ia == IA_RELOAD_SWAP) { /* reload */ if (toContType->isFloorDef()) mask = G_VisToPM(floor->visflags); else mask = G_TeamToPM(actor->team); G_EventInventoryReload(toContType->isFloorDef() ? *floor : *actor, mask, &item, toContType, item2); if (ia == IA_RELOAD) { return true; } else { /* ia == IA_RELOAD_SWAP */ item.setAmmoLeft(NONE_AMMO); item.setAmmoDef(nullptr); item.setDef(toItemBackup.ammoDef()); item.rotated = fromItemBackup.rotated; item.setAmount(toItemBackup.getAmount()); toContType = fromContType; if (toContType->isFloorDef()) { /* moveInInventory placed the swapped ammo in an available space, check where it was placed * so we can place it at the same place in the client, otherwise since fItem hasn't been removed * this could end in a different place in the client - will cause an error if trying to use it again */ item2 = actor->chr.inv.findInContainer(toContType->id, &item); assert(item2); fromItemBackup = item; fromItemBackup.setX(item2->getX()); fromItemBackup.setY(item2->getY()); } tx = fromItemBackup.getX(); ty = fromItemBackup.getY(); } } /* We moved an item to the floor - check how the client needs to be updated. */ if (toContType->isFloorDef()) { /* we have to link the temp floor container to the new floor edict or add * the item to an already existing floor edict - the floor container that * is already linked might be from a different entity (this might happen * in case of a throw by another actor) */ floor->setFloor(actor); /* A new container was created for the floor. */ if (newFloor) { /* Send item info to the clients */ G_CheckVis(floor); } else { /* use the backup item to use the old amount values, because the clients have to use the same actions * on the original amount. Otherwise they would end in a different amount of items as the server (+1) */ G_EventInventoryAdd(*floor, G_VisToPM(floor->visflags), 1); G_WriteItem(fromItemBackup, toContType->id, tx, ty); G_EventEnd(); /* Couldn't remove it before because that would remove the le from the client and would cause battlescape to crash * when trying to add back the swapped ammo above */ if (ia == IA_RELOAD_SWAP) G_EventInventoryDelete(*floor, G_VisToPM(floor->visflags), fromContType->id, fx, fy); } } else { G_EventInventoryAdd(*actor, G_TeamToPM(actor->team), 1); G_WriteItem(item, toContType->id, tx, ty); G_EventEnd(); } G_ReactionFireSettingsUpdate(actor, actor->chr.RFmode.getFmIdx(), actor->chr.RFmode.getHand(), actor->chr.RFmode.getWeapon()); /* Other players receive weapon info only. */ mask = G_VisToPM(actor->visflags) & ~G_TeamToPM(actor->team); if (mask) { if (fromContType->isRightDef() || fromContType->isLeftDef()) { G_EventInventoryDelete(*actor, mask, fromContType->id, fx, fy); } if (toContType->isRightDef() || toContType->isLeftDef()) { G_EventInventoryAdd(*actor, mask, 1); G_WriteItem(item, toContType->id, tx, ty); G_EventEnd(); } } return true; }
/** * @brief Applies morale changes to actors around a wounded or killed actor. * @note only called when mor_panic is not zero * @param[in] type Type of morale modifier (@sa morale_modifiers) * @param[in] victim An actor being a victim of the attack. * @param[in] attacker An actor being attacker in this attack. * @param[in] param Used to modify morale changes, for G_Damage() it is value of damage. * @sa G_Damage */ static void G_Morale (morale_modifiers type, const Edict* victim, const Edict* attacker, int param) { Actor* actor = nullptr; while ((actor = G_EdictsGetNextLivingActor(actor))) { /* this only applies to ET_ACTOR but not ET_ACTOR2x2 */ if (actor->type != ET_ACTOR) continue; if (G_IsCivilian(actor)) continue; /* morale damage depends on the damage */ float mod = mob_wound->value * param; if (type == ML_SHOOT) mod *= mob_shoot->value; /* death hurts morale even more than just damage */ if (type == ML_DEATH) mod += mob_death->value; /* seeing how someone gets shot increases the morale change */ if (actor == victim || (G_FrustumVis(actor, victim->origin) && G_ActorVis(actor, victim, false))) mod *= mof_watching->value; if (attacker != nullptr && actor->isSameTeamAs(attacker)) { /* teamkills are considered to be bad form, but won't cause an increased morale boost for the enemy */ /* morale boost isn't equal to morale loss (it's lower, but morale gets regenerated) */ if (victim->isSameTeamAs(attacker)) mod *= mof_teamkill->value; else mod *= mof_enemy->value; } /* seeing a civilian die is more "acceptable" */ if (G_IsCivilian(victim)) mod *= mof_civilian->value; /* if an ally (or in singleplayermode, as human, a civilian) got shot, lower the morale, don't heighten it. */ if (victim->isSameTeamAs(actor) || (G_IsCivilian(victim) && !G_IsAlien(actor) && G_IsSinglePlayer())) mod *= -1; if (attacker != nullptr) { /* if you stand near to the attacker or the victim, the morale change is higher. */ mod *= mor_default->value + pow(0.5f, VectorDist(actor->origin, victim->origin) / mor_distance->value) * mor_victim->value + pow(0.5f, VectorDist(actor->origin, attacker->origin) / mor_distance->value) * mor_attacker->value; } else { mod *= mor_default->value + pow(0.5f, VectorDist(actor->origin, victim->origin) / mor_distance->value) * mor_victim->value; } /* morale damage depends on the number of living allies */ mod *= (1 - mon_teamfactor->value) + mon_teamfactor->value * (level.num_spawned[victim->getTeam()] + 1) / (level.num_alive[victim->getTeam()] + 1); /* being hit isn't fun */ if (actor == victim) mod *= mor_pain->value; /* clamp new morale */ /*+0.9 to allow weapons like flamethrowers to inflict panic (typecast rounding) */ const int newMorale = actor->morale + (int) (MORALE_RANDOM(mod) + 0.9); if (newMorale > GET_MORALE(actor->chr.score.skills[ABILITY_MIND])) actor->setMorale(GET_MORALE(actor->chr.score.skills[ABILITY_MIND])); else if (newMorale < 0) actor->setMorale(0); else actor->setMorale(newMorale); /* send phys data */ G_SendStats(*actor); } }
/** * @brief Deals damage of a give type and amount to a target. * @param[in,out] target What we want to damage. * @param[in] fd The fire definition that defines what type of damage is dealt. * @param[in] damage The value of the damage. * @param[in] attacker The attacker. * @param[in] mock pseudo shooting - only for calculating mock values - nullptr for real shots * @param[in] impact impact location - @c nullptr for splash damage * @return @c true if damage could be dealt (even if it was 0) @c false otherwise * @sa G_SplashDamage * @sa G_TakeDamage * @sa G_PrintActorStats */ static bool G_Damage (Edict* target, const fireDef_t* fd, int damage, Actor* attacker, shot_mock_t* mock, const vec3_t impact) { assert(target); const bool stunEl = (fd->obj->dmgtype == gi.csi->damStunElectro); const bool stunGas = (fd->obj->dmgtype == gi.csi->damStunGas); const bool shock = (fd->obj->dmgtype == gi.csi->damShock); const bool smoke = (fd->obj->dmgtype == gi.csi->damSmoke); /* Breakables */ if (G_IsBrushModel(target) && G_IsBreakable(target)) { /* Breakables are immune to stun & shock damage. */ if (stunEl || stunGas || shock || mock || smoke) return false; if (damage >= target->HP) { /* don't reset the HP value here, this value is used to distinguish * between triggered destroy and a shoot */ assert(target->destroy); target->destroy(target); /* maybe the attacker is seeing something new? */ G_CheckVisTeamAll(attacker->getTeam(), 0, attacker); /* check if attacker appears/perishes for any other team */ G_CheckVis(attacker); } else { G_TakeDamage(target, damage); } return true; } /* Actors don't die again. */ if (!G_IsLivingActor(target)) return false; /* Now we know that the target is an actor */ Actor* victim = makeActor(target); /* only actors after this point - and they must have a teamdef */ assert(victim->chr.teamDef); const bool isRobot = CHRSH_IsTeamDefRobot(victim->chr.teamDef); /* Apply armour effects. */ if (damage > 0) { damage = G_ApplyProtection(victim, fd->dmgweight, damage); } else if (damage < 0) { /* Robots can't be healed. */ if (isRobot) return false; } Com_DPrintf(DEBUG_GAME, " Total damage: %d\n", damage); /* Apply difficulty settings. */ if (G_IsSinglePlayer()) { if (G_IsAlien(attacker) && !G_IsAlien(victim)) damage *= pow(1.18f, g_difficulty->value); else if (!G_IsAlien(attacker) && G_IsAlien(victim)) damage *= pow(1.18f, -g_difficulty->value); } assert(attacker->getTeam() >= 0 && attacker->getTeam() < MAX_TEAMS); assert(victim->getTeam() >= 0 && victim->getTeam() < MAX_TEAMS); if ((g_nodamage != nullptr && !g_nodamage->integer) || mock) { /* hit */ if (mock) { G_UpdateShotMock(mock, attacker, victim, damage); } else if (stunEl) { victim->addStun(damage); } else if (stunGas) { if (!isRobot) /* Can't stun robots with gas */ victim->addStun(damage); } else if (shock) { /* Only do this if it's not one from our own team ... they should have known that there was a flashbang coming. */ if (!isRobot && !victim->isSameTeamAs(attacker)) { /** @todo there should be a possible protection, too */ /* dazed entity wont reaction fire */ victim->removeReaction(); G_ActorReserveTUs(victim, 0, victim->chr.reservedTus.shot, victim->chr.reservedTus.crouch); /* flashbangs kill TUs */ G_ActorSetTU(victim, 0); G_SendStats(*victim); /* entity is dazed */ victim->setDazed(); G_EventSendState(G_VisToPM(victim->visflags), *victim); return !mock; } else { return false; } } else { if (damage < 0) { /* The 'attacker' is healing the victim. */ G_TreatActor(victim, fd, damage, attacker->getTeam()); } else { /* Real damage was dealt. */ G_DamageActor(victim, damage, impact); /* Update overall splash damage for stats/score. */ if (!mock && damage > 0 && fd->splrad) /**< Check for >0 and splrad to not count this as direct hit. */ G_UpdateHitScore(attacker, victim, fd, damage); } } } if (mock) return false; G_CheckDeathOrKnockout(victim, attacker, fd, damage); return true; }