static void G_ActorRevitalise (Edict* ent) { if (G_IsStunned(ent)) { G_RemoveStunned(ent); /** @todo have a look at the morale value of * the ent and maybe get into rage or panic? */ G_ActorModifyCounters(ent->link, ent, 1, 0, -1); G_GetFloorItems(ent); } G_ActorSetMaxs(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); G_PrintStats("%s is revitalized.", ent->chr.name); }
/** * @brief Reports and handles death or stun of an actor. If the HP of an actor is zero the actor * will die, otherwise the actor will get stunned. * @param[in] ent Pointer to an entity being killed or stunned actor. * @param[in] attacker Pointer to attacker - it must be notified about state of victim. * @todo Discuss whether stunned actor should really drop everything to floor. Maybe * it should drop only what he has in hands? Stunned actor can wake later during mission. */ bool G_ActorDieOrStun (Edict* ent, Edict* attacker) { bool state; if (ent->HP == 0) state = G_ActorDie(ent, attacker); else state = G_ActorStun(ent, attacker); /* no state change performed? */ if (!state) { gi.DPrintf("G_ActorDieOrStun: State wasn't changed\n"); return false; } if (!G_IsStunned(ent)) ent->solid = SOLID_NOT; /* send death */ G_EventActorDie(*ent, attacker != nullptr); /* handle inventory - drop everything but the armour to the floor */ G_InventoryToFloor(ent); G_ClientStateChange(ent->getPlayer(), ent, ~STATE_REACTION, false); /* check if the player appears/perishes, seen from other teams */ G_CheckVis(ent); /* check if the attacker appears/perishes, seen from other teams */ if (attacker) G_CheckVis(attacker); /* calc new vis for this player */ G_CheckVisTeamAll(ent->team, 0, attacker); /* unlink the floor container */ ent->resetFloor(); G_ReactionFireOnDead(ent); 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 Opens/closes a door * @note Use function for func_door * @todo Check if the door can be opened or closed - there should not be * anything in the way (e.g. an actor) */ static bool Door_Use (edict_t *door, edict_t *activator) { if (door->doorState == STATE_CLOSED) { door->doorState = STATE_OPENED; /* change rotation/origin and relink */ if (door->type == ET_DOOR) { if (door->dir & DOOR_OPEN_REVERSE) door->angles[door->dir & 3] -= DOOR_ROTATION_ANGLE; else door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE; } else if (door->type == ET_DOOR_SLIDING) { Door_SlidingUse(door); } gi.LinkEdict(door); /* maybe the server called this because the door starts opened */ if (G_MatchIsRunning()) { /* let everybody know, that the door opens */ G_EventDoorOpen(door); if (door->noise[0] != '\0') G_EventSpawnSound(PM_ALL, false, door, door->origin, door->noise); } } else if (door->doorState == STATE_OPENED) { door->doorState = STATE_CLOSED; /* change rotation and relink */ if (door->type == ET_DOOR) { if (door->dir & DOOR_OPEN_REVERSE) door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE; else door->angles[door->dir & 3] -= DOOR_ROTATION_ANGLE; } else if (door->type == ET_DOOR_SLIDING) { Door_SlidingUse(door); } gi.LinkEdict(door); /* closed is the standard, opened is handled above - we need an active * team here already */ if (G_MatchIsRunning()) { /* let everybody know, that the door closes */ G_EventDoorClose(door); if (door->noise[0] != '\0') G_EventSpawnSound(PM_ALL, false, door, door->origin, door->noise); } } else return false; /* Update model orientation */ gi.SetInlineModelOrientation(door->model, door->origin, door->angles); Com_DPrintf(DEBUG_GAME, "Server processed door movement.\n"); /* Update path finding table */ G_RecalcRouting(door->model); if (activator && G_IsLivingActor(activator)) { /* Check if the player appears/perishes, seen from other teams. */ G_CheckVis(activator, true); /* Calc new vis for the activator. */ G_CheckVisTeamAll(activator->team, false, activator); } return true; }
/** * @sa G_PlayerSoldiersCount */ void G_ClientEndRound (Player& player) { Player* p; const int lastTeamIndex = (G_GetActiveTeam() + level.teamOfs) % MAX_TEAMS; if (!G_IsAIPlayer(&player)) { /* inactive players can't end their inactive turn :) */ if (level.activeTeam != player.getTeam()) return; /* check for "team oszillation" */ if (level.framenum < level.nextEndRound) return; level.nextEndRound = level.framenum + 20; } /* only use this for teamplay matches like coopX or fight2on2 and above * also skip this for ai players, this is only called when all ai actors * have finished their 'thinking' */ if (!G_IsAIPlayer(&player) && sv_teamplay->integer) { /* check if all team members are ready */ if (!player.roundDone) { player.roundDone = true; G_EventEndRoundAnnounce(player); G_EventEnd(); } p = nullptr; while ((p = G_PlayerGetNextActiveHuman(p))) if (p->getTeam() == level.activeTeam && !p->roundDone && G_PlayerSoldiersCount(*p) > 0) return; p = nullptr; while ((p = G_PlayerGetNextActiveAI(p))) if (p->getTeam() == level.activeTeam && !p->roundDone && G_PlayerSoldiersCount(*p) > 0) return; } else { player.roundDone = true; } /* clear any remaining reaction fire */ G_ReactionFireOnEndTurn(); if (!G_IsAIPlayer(&player)) { if (g_lastseen->integer > 0) { Edict* ent = nullptr; while ((ent = G_EdictsGetNextActor(ent))) { if (G_IsAI(ent) && G_IsVisibleForTeam(ent, level.activeTeam)) { player.lastSeen = level.actualRound; break; } } if (level.actualRound - player.lastSeen > g_lastseen->integer) { Com_Printf("round end triggered by g_lastseen (player %i (team %i) last seen in round %i of %i rounds)\n", player.getNum(), level.activeTeam, player.lastSeen, level.actualRound); G_MatchEndTrigger(-1, 0); } } } /* let all the invisible players perish now */ G_CheckVisTeamAll(level.activeTeam, VIS_APPEAR, nullptr); G_GetNextActiveTeam(); AI_CheckRespawn(TEAM_ALIEN); /* no other team left? */ if (!G_MatchIsRunning()) return; if (lastTeamIndex > (level.activeTeam + level.teamOfs) % MAX_TEAMS) level.actualRound++; /* communicate next player in row to clients */ G_EventEndRound(); /* store the round start time to be able to abort the round after a give time */ level.roundstartTime = level.time; /* Wounded team members bleed */ G_BleedWounds(level.activeTeam); /* Update the state of stuned team-members. The actual statistics are sent below! */ G_UpdateStunState(level.activeTeam); /* Give the actors of the now active team their TUs. */ G_GiveTimeUnits(level.activeTeam); /* apply morale behaviour, reset reaction fire */ G_ReactionFireReset(level.activeTeam); if (mor_panic->integer) G_MoraleBehaviour(level.activeTeam); G_UpdateCarriedWeight(level.activeTeam); /* start ai - there is only one player for ai teams, and the last pointer must only * be updated for ai players */ p = G_GetPlayerForTeam(level.activeTeam); if (p == nullptr) gi.Error("Could not find player for team %i", level.activeTeam); /* finish off events */ G_EventEnd(); /* reset ready flag for every player on the current team (even ai players) */ p = nullptr; while ((p = G_PlayerGetNextActiveHuman(p))) { if (p->getTeam() == level.activeTeam) { p->roundDone = false; } } p = nullptr; while ((p = G_PlayerGetNextActiveAI(p))) { if (p->getTeam() == level.activeTeam) { p->roundDone = false; } } }
/** * @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 Turns an actor around * @param[in] ent the actor (edict) we are talking about * @param[in] dir the direction to turn the edict into, might be an action * @return Bitmask of visible (VIS_*) values * @sa G_CheckVisTeamAll */ int G_ActorDoTurn (Edict* ent, byte dir) { assert(ent->dir < CORE_DIRECTIONS); assert(dir < PATHFINDING_DIRECTIONS); /* * If dir is at least CORE_DIRECTIONS but less than FLYING_DIRECTIONS, * then the direction is an action. */ /** @todo If performing an action, ensure the actor is facing the direction * needed to perform the action if needed (climbing ladders). */ if (dir >= CORE_DIRECTIONS && dir < FLYING_DIRECTIONS) return 0; /* Clamp dir between 0 and CORE_DIRECTIONS - 1. */ dir &= (CORE_DIRECTIONS - 1); assert(dir < CORE_DIRECTIONS); /* Return if no rotation needs to be done. */ if (ent->dir == dir) return 0; /* calculate angle difference */ float angleDiv = directionAngles[dir] - directionAngles[ent->dir]; if (angleDiv > 180.0) angleDiv -= 360.0; if (angleDiv < -180.0) angleDiv += 360.0; /* prepare rotation - decide whether the actor turns around the left * shoulder or the right - this is needed the get the rotation vector * that is used below to check in each of the rotation steps * (1/8, 22.5 degree) whether something became visible while turning */ const byte* rot; int num; if (angleDiv > 0) { const int angleStep = (360.0 / CORE_DIRECTIONS); rot = dvleft; num = (angleDiv + angleStep / 2) / angleStep; } else { const int angleStep = (360.0 / CORE_DIRECTIONS); rot = dvright; num = (-angleDiv + angleStep / 2) / angleStep; } /* do rotation and vis checks */ int status = 0; /* check every angle (1/8 steps - on the way to the end direction) in the rotation * whether something becomes visible and stop before reaching the final direction * if this happened */ for (int i = 0; i < num; i++) { ent->dir = rot[ent->dir]; assert(ent->dir < CORE_DIRECTIONS); status |= G_CheckVisTeamAll(ent->team, 0, ent); } if (status & VIS_STOP) { /* send the turn */ G_EventActorTurn(*ent); } return status; }
/** * @brief Opens/closes a door * @note Use function for func_door * @todo Check if the door can be opened or closed - there should not be * anything in the way (e.g. an actor) */ static bool Door_Use (edict_t *door, edict_t *activator) { int opening = 1; if (door->doorState == STATE_CLOSED) { door->doorState = STATE_OPENED; opening = 1; } else if (door->doorState == STATE_OPENED) { door->doorState = STATE_CLOSED; opening = -1; } else return false; /* remember the old location */ AABB oldAABB; gi.GetInlineModelAABB(door->model, oldAABB); GridBox rerouteOldBox(oldAABB); /* change rotation and relink */ if (door->type == ET_DOOR) { if (door->dir & DOOR_OPEN_REVERSE) opening *= -1; door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE * opening; } else if (door->type == ET_DOOR_SLIDING) { Door_SlidingUse(door); } gi.LinkEdict(door); /* maybe the server called this because the door starts opened */ if (G_MatchIsRunning()) { /* let everybody know, that the door moves */ if (door->doorState == STATE_OPENED) G_EventDoorOpen(door); else G_EventDoorClose(door); if (door->noise[0] != '\0') { const playermask_t playerMask = G_GetClosePlayerMask(door->origin, UNIT_SIZE * 10); G_EventSpawnSound(playerMask, false, door, door->origin, door->noise); } } /* Update model orientation */ gi.SetInlineModelOrientation(door->model, door->origin, door->angles); AABB newAabb; gi.GetInlineModelAABB(door->model, newAabb); GridBox rerouteNewBox(newAabb); Com_DPrintf(DEBUG_GAME, "Server processed door movement.\n"); /* Update path finding table for the new location of the model */ G_RecalcRouting(door->model, rerouteOldBox); /* Update path finding table */ G_RecalcRouting(door->model, rerouteNewBox); if (activator && G_IsLivingActor(activator)) { /* Check if the player appears/perishes, seen from other teams. */ G_CheckVis(activator); /* Calc new vis for the activator. */ G_CheckVisTeamAll(activator->team, 0, activator); } return true; }
/** * @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; }