/** * @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); } }
void G_EventActorAppear (playermask_t playerMask, const Edict& check, const Edict* ent) { const int mask = G_TeamToPM(check.team) & playerMask; /* parsed in CL_ActorAppear */ G_EventAdd(playerMask, EV_ACTOR_APPEAR, check.number); gi.WriteShort(ent && ent->number > 0 ? ent->number : SKIP_LOCAL_ENTITY); gi.WriteByte(check.team); gi.WriteByte(check.chr.teamDef ? check.chr.teamDef->idx : NONE); gi.WriteByte(check.chr.gender); gi.WriteShort(check.chr.ucn); gi.WriteByte(check.pnum); gi.WriteGPos(check.pos); gi.WriteByte(check.dir); if (check.getRightHandItem()) { gi.WriteShort(check.getRightHandItem()->def()->idx); } else { gi.WriteShort(NONE); } if (check.getLeftHandItem()) { gi.WriteShort(check.getLeftHandItem()->def()->idx); } else { gi.WriteShort(NONE); } if (check.body == 0 || check.head == 0) { gi.Error("invalid body and/or head model indices"); } gi.WriteShort(check.body); gi.WriteShort(check.head); gi.WriteByte(check.chr.bodySkin); gi.WriteByte(check.chr.headSkin); /* strip the server private states */ gi.WriteShort(check.state & STATE_PUBLIC); gi.WriteByte(check.fieldSize); /* get the max values for TU and morale */ gi.WriteByte(G_ActorCalculateMaxTU(&check)); gi.WriteByte(std::min(MAX_SKILL, GET_MORALE(check.chr.score.skills[ABILITY_MIND]))); gi.WriteShort(check.chr.maxHP); G_EventEnd(); if (mask) { G_EventActorStateChange(mask, check); G_SendInventory(mask, check); } }
/** * @brief Used after spawning an actor to set some default values that are not read from the * network event. * @param ent The actor edict to set the values for. */ static void G_ClientAssignDefaultActorValues (edict_t *ent) { /* Mission Scores */ OBJZERO(scoreMission[scoreMissionNum]); ent->chr.scoreMission = &scoreMission[scoreMissionNum]; scoreMissionNum++; /* set initial vital statistics */ ent->HP = ent->chr.HP; ent->morale = ent->chr.morale; /** @todo for now, heal fully upon entering mission */ ent->morale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]); /* set models */ ent->body = gi.ModelIndex(CHRSH_CharGetBody(&ent->chr)); ent->head = gi.ModelIndex(CHRSH_CharGetHead(&ent->chr)); }
/** * @brief Heals a target and treats wounds. * @param[in,out] target Pointer to the actor who we want to treat. * @param[in] fd Pointer to the firedef used to heal the target. * @param[in] heal The value of the damage to heal. * @param[in] healerTeam The index of the team of the healer. */ void G_TreatActor (Actor* target, const fireDef_t* const fd, const int heal, const int healerTeam) { assert(target->chr.teamDef); /* Treat wounds */ if (fd->dmgweight == gi.csi->damNormal) { int mostWounded = 0; woundInfo_t* wounds = &target->chr.wounds; /* Find the worst not treated wound */ for (int bodyPart = 0; bodyPart < target->chr.teamDef->bodyTemplate->numBodyParts(); ++bodyPart) if (wounds->woundLevel[bodyPart] > wounds->woundLevel[mostWounded]) mostWounded = bodyPart; if (wounds->woundLevel[mostWounded] > 0) { const int woundsHealed = std::min(static_cast<int>(abs(heal) / target->chr.teamDef->bodyTemplate->bleedingFactor(mostWounded)), wounds->woundLevel[mostWounded]); G_TakeDamage(target, heal); wounds->woundLevel[mostWounded] -= woundsHealed; wounds->treatmentLevel[mostWounded] += woundsHealed; /* Update stats here to get info on how many HP the target received. */ if (target->chr.scoreMission) target->chr.scoreMission->heal += abs(heal); } } /* Treat stunned actors */ if (fd->dmgweight == gi.csi->damStunElectro && target->isStunned()) { if (CHRSH_IsTeamDefAlien(target->chr.teamDef) && target->getTeam() != healerTeam) /** @todo According to specs it should only be possible to use the medikit to keep an alien sedated when * 'live alien' is researched, is it possible to find if a tech is researched here? */ target->setStun(std::min(255, target->getStun() - heal)); else target->setStun(std::max(0, target->getStun() + heal)); G_ActorCheckRevitalise(target); } /* Increase morale */ if (fd->dmgweight == gi.csi->damShock) target->setMorale(std::min(GET_MORALE(target->chr.score.skills[ABILITY_MIND]), target->morale - heal)); G_SendWoundStats(target); }
/** * @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 Generates a skill and ability set for any character. * @param[in] chr Pointer to the character, for which we generate stats. * @param[in] multiplayer If this is true we use the skill values from @c soldier_mp * mulitplayer is a special case here */ void CHRSH_CharGenAbilitySkills (character_t * chr, bool multiplayer) { int i; const int (*chrTemplate)[2]; const teamDef_t *td; const chrTemplate_t *ct; td = chr->teamDef; /* Add modifiers for difficulty setting here! */ if (multiplayer && td->race == RACE_PHALANX_HUMAN) { for (i = 0; i < td->numTemplates; i++) { if (Q_streq(td->characterTemplates[i]->id, "soldier_mp")) { ct = td->characterTemplates[i]; break; } } if (i >= td->numTemplates) Sys_Error("CHRSH_CharGenAbilitySkills: No multiplayer character template found (soldier_mp)"); } else if (td->characterTemplates[0]) { if (td->numTemplates > 1) { float sumRate = 0.0; for (i = 0; i < td->numTemplates; i++) { ct = td->characterTemplates[i]; sumRate += ct->rate; } if (sumRate) { const float soldierRoll = frand(); float curRate = 0.0; for (ct = td->characterTemplates[0]; ct->id; ct++) { curRate += ct->rate; if (curRate && soldierRoll <= (curRate / sumRate)) break; } } else /* No rates or all set to 0 default to first template */ ct = td->characterTemplates[0]; } else /* Only one template */ ct = td->characterTemplates[0]; } else Sys_Error("CHRSH_CharGenAbilitySkills: No character template for team %s!", td->id); chrTemplate = ct->skills; assert(chrTemplate); /* Abilities and skills -- random within the range */ for (i = 0; i < SKILL_NUM_TYPES; i++) { const int abilityWindow = chrTemplate[i][1] - chrTemplate[i][0]; /* Reminder: In case if abilityWindow==0 the ability will be set to the lower limit. */ const int temp = (frand() * abilityWindow) + chrTemplate[i][0]; chr->score.skills[i] = temp; chr->score.initialSkills[i] = temp; } { /* Health. */ const int abilityWindow = chrTemplate[i][1] - chrTemplate[i][0]; const int temp = (frand() * abilityWindow) + chrTemplate[i][0]; chr->score.initialSkills[SKILL_NUM_TYPES] = temp; chr->maxHP = temp; chr->HP = temp; } /* Morale */ chr->morale = GET_MORALE(chr->score.skills[ABILITY_MIND]); if (chr->morale >= MAX_SKILL) chr->morale = MAX_SKILL; /* Initialize the experience values */ for (i = 0; i <= SKILL_NUM_TYPES; i++) { chr->score.experience[i] = 0; } }
/** * @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); } }