/** * @brief Checks whether the morale handling is activated for this game. This is always * the case in singleplayer matches, and might be disabled for multiplayer matches. * @return @c true if the morale is activated for this game. */ static bool G_IsMoraleEnabled (int team) { if (G_IsSinglePlayer()) return true; /* multiplayer */ if (team == TEAM_CIVILIAN || sv_enablemorale->integer == 1) return true; return false; }
/** * @brief Check whether a forced turn end should be executed */ void G_CheckForceEndRound (void) { /* check for roundlimits in multiplayer only */ if (!sv_roundtimelimit->integer || G_IsSinglePlayer()) return; if (!G_MatchIsRunning()) return; if (level.time != ceil(level.time)) return; const int diff = level.roundstartTime + sv_roundtimelimit->integer - level.time; switch (diff) { case 240: gi.BroadcastPrintf(PRINT_HUD, _("4 minutes left until forced turn end.")); return; case 180: gi.BroadcastPrintf(PRINT_HUD, _("3 minutes left until forced turn end.")); return; case 120: gi.BroadcastPrintf(PRINT_HUD, _("2 minutes left until forced turn end.")); return; case 60: gi.BroadcastPrintf(PRINT_HUD, _("1 minute left until forced turn end.")); return; case 30: gi.BroadcastPrintf(PRINT_HUD, _("30 seconds left until forced turn end.")); return; case 15: gi.BroadcastPrintf(PRINT_HUD, _("15 seconds left until forced turn end.")); return; } /* active team still has time left */ if (level.time < level.roundstartTime + sv_roundtimelimit->integer) return; gi.BroadcastPrintf(PRINT_HUD, _("Current active team hit the max round time.")); /* store this in a local variable, as the global variable is changed in G_ClientEndRound */ const int activeTeam = level.activeTeam; /* set all team members to ready (only human players) */ Player* p = nullptr; while ((p = G_PlayerGetNextActiveHuman(p))) { if (p->getTeam() == activeTeam) { G_ClientEndRound(*p); level.nextEndRound = level.framenum; } } level.roundstartTime = level.time; }
/** * @brief info_player_start (1 0 0) (-16 -16 -24) (16 16 32) * Starting point for a player. * "team" the number of the team for this player starting point * "0" is reserved for civilians and critters (use info_civilian_start instead) */ static void SP_player_start (Edict* ent) { /* only used in multi player */ if (G_IsSinglePlayer()) { G_FreeEdict(ent); return; } /** @todo Wrong place here */ /* maybe there are already the max soldiers allowed per team connected */ if (sv_maxsoldiersperteam->integer > level.num_spawnpoints[ent->team]) { ent->STUN = 0; ent->HP = INITIAL_HP; G_ActorSpawn(ent); } else G_FreeEdict(ent); }
/** * @brief Creates a server's entity / program execution context * by parsing textual entity definitions out of an ent file. * @sa CM_EntityString * @sa SV_SpawnServer */ void G_SpawnEntities (const char* mapname, bool day, const char* entities) { int entnum; G_FreeTags(TAG_LEVEL); OBJZERO(level); level.pathingMap = (pathing_t*)G_TagMalloc(sizeof(*level.pathingMap), TAG_LEVEL); G_EdictsInit(); /* initialize reactionFire data */ G_ReactionFireTargetsInit(); Q_strncpyz(level.mapname, mapname, sizeof(level.mapname)); level.day = day; G_ResetClientData(); level.activeTeam = TEAM_NO_ACTIVE; level.actualRound = 1; level.hurtAliens = sv_hurtaliens->integer; /* parse ents */ entnum = 0; while (1) { Edict* ent; /* parse the opening brace */ const char* token = Com_Parse(&entities); if (!entities) break; if (token[0] != '{') gi.Error("ED_LoadFromFile: found %s when expecting {", token); ent = G_Spawn(); entities = ED_ParseEdict(entities, ent); ent->mapNum = entnum++; /* Set the position of the entity */ VecToPos(ent->origin, ent->pos); /* Call this entity's specific initializer (sets ent->type) */ ED_CallSpawn(ent); /* if this entity is an bbox (e.g. actor), then center its origin based on its position */ if (ent->solid == SOLID_BBOX) G_EdictCalcOrigin(ent); } /* spawn ai players, if needed */ if (level.num_spawnpoints[TEAM_CIVILIAN]) { if (AI_CreatePlayer(TEAM_CIVILIAN) == nullptr) gi.DPrintf("Could not create civilian\n"); } if ((G_IsSinglePlayer() || ai_multiplayeraliens->integer) && level.num_spawnpoints[TEAM_ALIEN]) { if (AI_CreatePlayer(TEAM_ALIEN) == nullptr) gi.DPrintf("Could not create alien\n"); } Com_Printf("Used inventory slots after ai spawn: %i\n", game.i.GetUsedSlots()); G_FindEdictGroups(); }
/** * @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) { Edict* ent = nullptr; while ((ent = G_EdictsGetNextInUse(ent))) { /* this only applies to ET_ACTOR but not ET_ACTOR2x2 */ if (ent->type != ET_ACTOR) continue; if (G_IsDead(ent)) continue; if (ent->team == TEAM_CIVILIAN) continue; /* morale damage depends on the damage */ float 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 != nullptr && 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 && 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.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; /* clamp new morale */ /*+0.9 to allow weapons like flamethrowers to inflict panic (typecast rounding) */ const int 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 - nullptr for real shots * @param[in] impact impact location - @c nullptr for splash damage * @sa G_SplashDamage * @sa G_TakeDamage * @sa G_PrintActorStats */ static void G_Damage (Edict* target, const fireDef_t* fd, int damage, Edict* 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 (G_IsSinglePlayer()) { 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 != nullptr && !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(target->getPlayer(), 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); }